mirror of
https://github.com/apache/cloudstack
synced 2026-08-02 05:26:35 +00:00
Feature: Key Management Service (KMS) (#12711)
Some checks are pending
Build / build (push) Waiting to run
Simulator CI / build (push) Waiting to run
Simulator CI / test (component/find_hosts_for_migration component/test_acl_isolatednetwork component/test_acl_isolatednetwork_delete component/test_acl_listsnapshot) (push) Blocked by required conditions
Simulator CI / test (component/test_acl_listvm component/test_acl_listvolume) (push) Blocked by required conditions
Simulator CI / test (component/test_acl_sharednetwork component/test_acl_sharednetwork_deployVM-impersonation component/test_user_private_gateway component/test_user_shared_network) (push) Blocked by required conditions
Simulator CI / test (component/test_affinity_groups_projects component/test_allocation_states component/test_assign_vm) (push) Blocked by required conditions
Simulator CI / test (component/test_concurrent_snapshots_limit component/test_cpu_domain_limits component/test_cpu_limits component/test_cpu_max_limits component/test_cpu_project_limits component/test_deploy_vm_userdata_multi_nic component/test_deploy_vm_lease) (push) Blocked by required conditions
Simulator CI / test (component/test_egress_fw_rules component/test_invalid_gw_nm component/test_ip_reservation) (push) Blocked by required conditions
Simulator CI / test (component/test_lb_secondary_ip component/test_list_nics component/test_list_pod component/test_memory_limits) (push) Blocked by required conditions
Simulator CI / test (component/test_mm_domain_limits component/test_mm_max_limits component/test_mm_project_limits component/test_network_offering component/test_non_contiguous_vlan) (push) Blocked by required conditions
Simulator CI / test (component/test_persistent_networks component/test_project_configs component/test_project_limits component/test_project_resources) (push) Blocked by required conditions
Simulator CI / test (component/test_project_usage component/test_protocol_number_security_group component/test_public_ip component/test_resource_limits component/test_resource_limit_tags) (push) Blocked by required conditions
Simulator CI / test (component/test_regions_accounts component/test_routers component/test_snapshots component/test_stopped_vm component/test_tags component/test_templates component/test_updateResourceCount component/test_update_vm) (push) Blocked by required conditions
Simulator CI / test (component/test_volumes component/test_vpc component/test_vpc_distributed_routing_offering component/test_vpc_network component/test_vpc_offerings component/test_vpc_routers component/test_vpn_users component/test_vpc_network_lbrules) (push) Blocked by required conditions
Simulator CI / test (smoke/test_accounts smoke/test_account_access smoke/test_affinity_groups smoke/test_affinity_groups_projects smoke/test_annotations smoke/test_async_job smoke/test_attach_multiple_volumes smoke/test_backup_recovery_dummy smoke/test_certauthority_… (push) Blocked by required conditions
Simulator CI / test (smoke/test_cluster_drs smoke/test_dynamicroles smoke/test_enable_account_settings_for_domain smoke/test_enable_role_based_users_in_projects smoke/test_events_resource smoke/test_global_settings smoke/test_guest_vlan_range smoke/test_host_maintena… (push) Blocked by required conditions
Simulator CI / test (smoke/test_list_accounts smoke/test_list_disk_offerings smoke/test_list_domains smoke/test_list_hosts smoke/test_list_service_offerings smoke/test_list_storage_pools smoke/test_list_volumes) (push) Blocked by required conditions
Simulator CI / test (smoke/test_network smoke/test_network_acl smoke/test_network_ipv6 smoke/test_network_permissions smoke/test_nic smoke/test_nic_adapter_type smoke/test_non_contigiousvlan smoke/test_object_stores smoke/test_outofbandmanagement smoke/test_outofband… (push) Blocked by required conditions
Simulator CI / test (smoke/test_router_dhcphosts smoke/test_router_dns smoke/test_router_dnsservice smoke/test_routers smoke/test_routers_iptables_default_policy smoke/test_routers_network_ops smoke/test_scale_vm smoke/test_secondary_storage smoke/test_service_offeri… (push) Blocked by required conditions
CodeQL Analysis / CodeQL (actions) (push) Waiting to run
Docker Image Build / build (push) Waiting to run
Sonar Quality Check (Main) / Sonar JaCoCo Coverage (push) Waiting to run
License Check / build (push) Waiting to run
UI Build / build (push) Waiting to run
Some checks are pending
Build / build (push) Waiting to run
Simulator CI / build (push) Waiting to run
Simulator CI / test (component/find_hosts_for_migration component/test_acl_isolatednetwork component/test_acl_isolatednetwork_delete component/test_acl_listsnapshot) (push) Blocked by required conditions
Simulator CI / test (component/test_acl_listvm component/test_acl_listvolume) (push) Blocked by required conditions
Simulator CI / test (component/test_acl_sharednetwork component/test_acl_sharednetwork_deployVM-impersonation component/test_user_private_gateway component/test_user_shared_network) (push) Blocked by required conditions
Simulator CI / test (component/test_affinity_groups_projects component/test_allocation_states component/test_assign_vm) (push) Blocked by required conditions
Simulator CI / test (component/test_concurrent_snapshots_limit component/test_cpu_domain_limits component/test_cpu_limits component/test_cpu_max_limits component/test_cpu_project_limits component/test_deploy_vm_userdata_multi_nic component/test_deploy_vm_lease) (push) Blocked by required conditions
Simulator CI / test (component/test_egress_fw_rules component/test_invalid_gw_nm component/test_ip_reservation) (push) Blocked by required conditions
Simulator CI / test (component/test_lb_secondary_ip component/test_list_nics component/test_list_pod component/test_memory_limits) (push) Blocked by required conditions
Simulator CI / test (component/test_mm_domain_limits component/test_mm_max_limits component/test_mm_project_limits component/test_network_offering component/test_non_contiguous_vlan) (push) Blocked by required conditions
Simulator CI / test (component/test_persistent_networks component/test_project_configs component/test_project_limits component/test_project_resources) (push) Blocked by required conditions
Simulator CI / test (component/test_project_usage component/test_protocol_number_security_group component/test_public_ip component/test_resource_limits component/test_resource_limit_tags) (push) Blocked by required conditions
Simulator CI / test (component/test_regions_accounts component/test_routers component/test_snapshots component/test_stopped_vm component/test_tags component/test_templates component/test_updateResourceCount component/test_update_vm) (push) Blocked by required conditions
Simulator CI / test (component/test_volumes component/test_vpc component/test_vpc_distributed_routing_offering component/test_vpc_network component/test_vpc_offerings component/test_vpc_routers component/test_vpn_users component/test_vpc_network_lbrules) (push) Blocked by required conditions
Simulator CI / test (smoke/test_accounts smoke/test_account_access smoke/test_affinity_groups smoke/test_affinity_groups_projects smoke/test_annotations smoke/test_async_job smoke/test_attach_multiple_volumes smoke/test_backup_recovery_dummy smoke/test_certauthority_… (push) Blocked by required conditions
Simulator CI / test (smoke/test_cluster_drs smoke/test_dynamicroles smoke/test_enable_account_settings_for_domain smoke/test_enable_role_based_users_in_projects smoke/test_events_resource smoke/test_global_settings smoke/test_guest_vlan_range smoke/test_host_maintena… (push) Blocked by required conditions
Simulator CI / test (smoke/test_list_accounts smoke/test_list_disk_offerings smoke/test_list_domains smoke/test_list_hosts smoke/test_list_service_offerings smoke/test_list_storage_pools smoke/test_list_volumes) (push) Blocked by required conditions
Simulator CI / test (smoke/test_network smoke/test_network_acl smoke/test_network_ipv6 smoke/test_network_permissions smoke/test_nic smoke/test_nic_adapter_type smoke/test_non_contigiousvlan smoke/test_object_stores smoke/test_outofbandmanagement smoke/test_outofband… (push) Blocked by required conditions
Simulator CI / test (smoke/test_router_dhcphosts smoke/test_router_dns smoke/test_router_dnsservice smoke/test_routers smoke/test_routers_iptables_default_policy smoke/test_routers_network_ops smoke/test_scale_vm smoke/test_secondary_storage smoke/test_service_offeri… (push) Blocked by required conditions
CodeQL Analysis / CodeQL (actions) (push) Waiting to run
Docker Image Build / build (push) Waiting to run
Sonar Quality Check (Main) / Sonar JaCoCo Coverage (push) Waiting to run
License Check / build (push) Waiting to run
UI Build / build (push) Waiting to run
* Add KMS framework * integrate volume encryption with kms * rotate keys wrapped with older versions * allow adding kms key in deploy vm * temp commit * Add some tests * fixups and some ui changes * Change wrapping algo for pkcs * fixups * fix list apis * directly create the key in the store * Add missing license * fixup * Fix missing labels and label generation * Fixup ui bug * fixups * Add smoke test * Fix test failures * fixups * Address comments * fix failing tests * Address comments * Fix pre-commit error * fixup * Fix failing unit tests * fixup * fix UI bug * fixup createHSM in marvin library * Fix bugs * fix failing test * fixup * fixup * fix marvin lib & tests * fix marvin tests * fixup * Make HSM Profile domain scoped * fixup
This commit is contained in:
parent
1853435185
commit
d2c8aa7dff
@ -71,6 +71,11 @@
|
||||
<artifactId>cloud-framework-direct-download</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-kms</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@ -36,6 +36,8 @@ import org.apache.cloudstack.gpu.GpuCard;
|
||||
import org.apache.cloudstack.gpu.GpuDevice;
|
||||
import org.apache.cloudstack.gpu.VgpuProfile;
|
||||
import org.apache.cloudstack.ha.HAConfig;
|
||||
import org.apache.cloudstack.kms.HSMProfile;
|
||||
import org.apache.cloudstack.kms.KMSKey;
|
||||
import org.apache.cloudstack.network.BgpPeer;
|
||||
import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMap;
|
||||
import org.apache.cloudstack.quota.QuotaTariff;
|
||||
@ -272,6 +274,20 @@ public class EventTypes {
|
||||
public static final String EVENT_CA_CERTIFICATE_REVOKE = "CA.CERTIFICATE.REVOKE";
|
||||
public static final String EVENT_CA_CERTIFICATE_PROVISION = "CA.CERTIFICATE.PROVISION";
|
||||
|
||||
// KMS (Key Management Service) events
|
||||
public static final String EVENT_KMS_KEY_WRAP = "KMS.KEY.WRAP";
|
||||
public static final String EVENT_KMS_KEY_UNWRAP = "KMS.KEY.UNWRAP";
|
||||
public static final String EVENT_KMS_KEY_CREATE = "KMS.KEY.CREATE";
|
||||
public static final String EVENT_KMS_KEY_UPDATE = "KMS.KEY.UPDATE";
|
||||
public static final String EVENT_KMS_KEY_ROTATE = "KMS.KEY.ROTATE";
|
||||
public static final String EVENT_KMS_KEY_DELETE = "KMS.KEY.DELETE";
|
||||
public static final String EVENT_VOLUME_MIGRATE_TO_KMS = "VOLUME.MIGRATE.TO.KMS";
|
||||
|
||||
// HSM Profile events
|
||||
public static final String EVENT_HSM_PROFILE_CREATE = "HSM.PROFILE.CREATE";
|
||||
public static final String EVENT_HSM_PROFILE_UPDATE = "HSM.PROFILE.UPDATE";
|
||||
public static final String EVENT_HSM_PROFILE_DELETE = "HSM.PROFILE.DELETE";
|
||||
|
||||
// Account events
|
||||
public static final String EVENT_ACCOUNT_ENABLE = "ACCOUNT.ENABLE";
|
||||
public static final String EVENT_ACCOUNT_DISABLE = "ACCOUNT.DISABLE";
|
||||
@ -1020,6 +1036,20 @@ public class EventTypes {
|
||||
entityEventDetails.put(EVENT_VOLUME_RECOVER, Volume.class);
|
||||
entityEventDetails.put(EVENT_VOLUME_CHANGE_DISK_OFFERING, Volume.class);
|
||||
|
||||
// KMS Key Events
|
||||
entityEventDetails.put(EVENT_KMS_KEY_CREATE, KMSKey.class);
|
||||
entityEventDetails.put(EVENT_KMS_KEY_UPDATE, KMSKey.class);
|
||||
entityEventDetails.put(EVENT_KMS_KEY_UNWRAP, KMSKey.class);
|
||||
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);
|
||||
entityEventDetails.put(EVENT_HSM_PROFILE_UPDATE, HSMProfile.class);
|
||||
entityEventDetails.put(EVENT_HSM_PROFILE_DELETE, HSMProfile.class);
|
||||
|
||||
// Domains
|
||||
entityEventDetails.put(EVENT_DOMAIN_CREATE, Domain.class);
|
||||
entityEventDetails.put(EVENT_DOMAIN_DELETE, Domain.class);
|
||||
|
||||
@ -23,6 +23,7 @@ public class DiskOfferingInfo {
|
||||
private Long _size;
|
||||
private Long _minIops;
|
||||
private Long _maxIops;
|
||||
private Long _kmsKeyId;
|
||||
|
||||
public DiskOfferingInfo() {
|
||||
}
|
||||
@ -38,6 +39,14 @@ public class DiskOfferingInfo {
|
||||
_maxIops = maxIops;
|
||||
}
|
||||
|
||||
public DiskOfferingInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops, Long kmsKeyId) {
|
||||
_diskOffering = diskOffering;
|
||||
_size = size;
|
||||
_minIops = minIops;
|
||||
_maxIops = maxIops;
|
||||
_kmsKeyId = kmsKeyId;
|
||||
}
|
||||
|
||||
public void setDiskOffering(DiskOffering diskOffering) {
|
||||
_diskOffering = diskOffering;
|
||||
}
|
||||
@ -69,4 +78,12 @@ public class DiskOfferingInfo {
|
||||
public Long getMaxIops() {
|
||||
return _maxIops;
|
||||
}
|
||||
|
||||
public void setKmsKeyId(Long kmsKeyId) {
|
||||
_kmsKeyId = kmsKeyId;
|
||||
}
|
||||
|
||||
public Long getKmsKeyId() {
|
||||
return _kmsKeyId;
|
||||
}
|
||||
}
|
||||
|
||||
@ -275,6 +275,14 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
|
||||
|
||||
void setPassphraseId(Long id);
|
||||
|
||||
Long getKmsKeyId();
|
||||
|
||||
void setKmsKeyId(Long id);
|
||||
|
||||
Long getKmsWrappedKeyId();
|
||||
|
||||
void setKmsWrappedKeyId(Long id);
|
||||
|
||||
String getEncryptFormat();
|
||||
|
||||
void setEncryptFormat(String encryptFormat);
|
||||
|
||||
@ -72,7 +72,7 @@ public interface VolumeApiService {
|
||||
Volume allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationException;
|
||||
|
||||
Volume allocVolume(long ownerId, Long zoneId, Long diskOfferingId, Long vmId, Long snapshotId, String name,
|
||||
Long cmdSize, Boolean displayVolume, Long cmdMinIops, Long cmdMaxIops, String customId)
|
||||
Long cmdSize, Boolean displayVolume, Long cmdMinIops, Long cmdMaxIops, String customId, Long kmsKeyId)
|
||||
throws ResourceAllocationException;
|
||||
|
||||
/**
|
||||
|
||||
@ -82,7 +82,7 @@ public class DiskProfile {
|
||||
null);
|
||||
this.hyperType = hyperType;
|
||||
this.provisioningType = offering.getProvisioningType();
|
||||
this.requiresEncryption = offering.getEncrypt() || vol.getPassphraseId() != null;
|
||||
this.requiresEncryption = offering.getEncrypt() || vol.getPassphraseId() != null || vol.getKmsKeyId() != null;
|
||||
}
|
||||
|
||||
public DiskProfile(DiskProfile dp) {
|
||||
|
||||
@ -229,7 +229,7 @@ public interface UserVmService {
|
||||
String userData, Long userDataId, String userDataDetails, List<String> sshKeyPairs, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard,
|
||||
List<Long> affinityGroupIdList, Map<String, String> customParameter, String customId, Map<String, Map<Integer, String>> dhcpOptionMap,
|
||||
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
|
||||
Map<String, String> userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException,
|
||||
Map<String, String> userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Long rootDiskKmsKeyId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException,
|
||||
ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
|
||||
|
||||
/**
|
||||
@ -305,7 +305,7 @@ public interface UserVmService {
|
||||
List<Long> securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, List<VmDiskInfo> dataDiskInfoList, String group, HypervisorType hypervisor,
|
||||
HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List<String> sshKeyPairs, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard,
|
||||
List<Long> affinityGroupIdList, Map<String, String> customParameters, String customId, Map<String, Map<Integer, String>> dhcpOptionMap,
|
||||
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap, Map<String, String> userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
|
||||
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap, Map<String, String> userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Long rootDiskKmsKeyId, String vmType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
|
||||
|
||||
/**
|
||||
* Creates a User VM in Advanced Zone (Security Group feature is disabled)
|
||||
@ -377,7 +377,7 @@ public interface UserVmService {
|
||||
String hostName, String displayName, Long diskOfferingId, Long diskSize, List<VmDiskInfo> dataDiskInfoList, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData,
|
||||
Long userDataId, String userDataDetails, List<String> sshKeyPairs, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List<Long> affinityGroupIdList,
|
||||
Map<String, String> customParameters, String customId, Map<String, Map<Integer, String>> dhcpOptionMap, Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
|
||||
Map<String, String> templateOvfPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId, Volume volume, Snapshot snapshot)
|
||||
Map<String, String> templateOvfPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId, Long rootDiskKmsKeyId, Volume volume, Snapshot snapshot)
|
||||
|
||||
throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException;
|
||||
|
||||
|
||||
@ -33,6 +33,11 @@ public class VmDiskInfo extends DiskOfferingInfo {
|
||||
_deviceId = deviceId;
|
||||
}
|
||||
|
||||
public VmDiskInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops, Long deviceId, Long kmsKeyId) {
|
||||
super(diskOffering, size, minIops, maxIops, kmsKeyId);
|
||||
_deviceId = deviceId;
|
||||
}
|
||||
|
||||
public Long getDeviceId() {
|
||||
return _deviceId;
|
||||
}
|
||||
|
||||
@ -89,7 +89,9 @@ public enum ApiCommandResourceType {
|
||||
KubernetesSupportedVersion(null),
|
||||
SharedFS(org.apache.cloudstack.storage.sharedfs.SharedFS.class),
|
||||
Extension(org.apache.cloudstack.extension.Extension.class),
|
||||
ExtensionCustomAction(org.apache.cloudstack.extension.ExtensionCustomAction.class);
|
||||
ExtensionCustomAction(org.apache.cloudstack.extension.ExtensionCustomAction.class),
|
||||
KmsKey(org.apache.cloudstack.kms.KMSKey.class),
|
||||
HsmProfile(org.apache.cloudstack.kms.HSMProfile.class);
|
||||
|
||||
private final Class<?> clazz;
|
||||
|
||||
|
||||
@ -202,6 +202,7 @@ public class ApiConstants {
|
||||
public static final String UTILIZATION = "utilization";
|
||||
public static final String DRIVER = "driver";
|
||||
public static final String ROOT_DISK_SIZE = "rootdisksize";
|
||||
public static final String ROOT_DISK_KMS_KEY_ID = "rootdiskkmskeyid";
|
||||
public static final String DHCP_OPTIONS_NETWORK_LIST = "dhcpoptionsnetworklist";
|
||||
public static final String DHCP_OPTIONS = "dhcpoptions";
|
||||
public static final String DHCP_PREFIX = "dhcp:";
|
||||
@ -886,7 +887,14 @@ public class ApiConstants {
|
||||
public static final String ITEMS = "items";
|
||||
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 = "kmskey";
|
||||
public static final String KMS_KEY_ID = "kmskeyid";
|
||||
public static final String KMS_KEY_VERSION = "kmskeyversion";
|
||||
public static final String KEK_LABEL = "keklabel";
|
||||
public static final String KEY_BITS = "keybits";
|
||||
public static final String IS_TAGGED = "istagged";
|
||||
public static final String INSTANCE_NAME = "instancename";
|
||||
public static final String CONSIDER_LAST_HOST = "considerlasthost";
|
||||
|
||||
@ -281,7 +281,8 @@ public interface ResponseGenerator {
|
||||
|
||||
List<UserVmResponse> createUserVmResponse(ResponseView view, String objectName, UserVm... userVms);
|
||||
|
||||
List<UserVmResponse> createUserVmResponse(ResponseView view, String objectName, EnumSet<VMDetails> details, UserVm... userVms);
|
||||
List<UserVmResponse> createUserVmResponse(ResponseView view, String objectName, EnumSet<VMDetails> details,
|
||||
UserVm... userVms);
|
||||
|
||||
SystemVmResponse createSystemVmResponse(VirtualMachine systemVM);
|
||||
|
||||
@ -307,11 +308,13 @@ public interface ResponseGenerator {
|
||||
|
||||
LoadBalancerResponse createLoadBalancerResponse(LoadBalancer loadBalancer);
|
||||
|
||||
LBStickinessResponse createLBStickinessPolicyResponse(List<? extends StickinessPolicy> stickinessPolicies, LoadBalancer lb);
|
||||
LBStickinessResponse createLBStickinessPolicyResponse(List<? extends StickinessPolicy> stickinessPolicies,
|
||||
LoadBalancer lb);
|
||||
|
||||
LBStickinessResponse createLBStickinessPolicyResponse(StickinessPolicy stickinessPolicy, LoadBalancer lb);
|
||||
|
||||
LBHealthCheckResponse createLBHealthCheckPolicyResponse(List<? extends HealthCheckPolicy> healthcheckPolicies, LoadBalancer lb);
|
||||
LBHealthCheckResponse createLBHealthCheckPolicyResponse(List<? extends HealthCheckPolicy> healthcheckPolicies,
|
||||
LoadBalancer lb);
|
||||
|
||||
LBHealthCheckResponse createLBHealthCheckPolicyResponse(HealthCheckPolicy healthcheckPolicy, LoadBalancer lb);
|
||||
|
||||
@ -319,7 +322,8 @@ public interface ResponseGenerator {
|
||||
|
||||
PodResponse createMinimalPodResponse(Pod pod);
|
||||
|
||||
ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities, Boolean showResourceIcon);
|
||||
ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities,
|
||||
Boolean showResourceIcon);
|
||||
|
||||
DataCenterGuestIpv6PrefixResponse createDataCenterGuestIpv6PrefixResponse(DataCenterGuestIpv6Prefix prefix);
|
||||
|
||||
@ -361,7 +365,8 @@ public interface ResponseGenerator {
|
||||
|
||||
List<TemplateResponse> createTemplateResponses(ResponseView view, long templateId, Long zoneId, boolean readyOnly);
|
||||
|
||||
List<TemplateResponse> createTemplateResponses(ResponseView view, long templateId, Long snapshotId, Long volumeId, boolean readyOnly);
|
||||
List<TemplateResponse> createTemplateResponses(ResponseView view, long templateId, Long snapshotId, Long volumeId,
|
||||
boolean readyOnly);
|
||||
|
||||
SecurityGroupResponse createSecurityGroupResponseFromSecurityGroupRule(List<? extends SecurityRule> securityRules);
|
||||
|
||||
@ -380,14 +385,15 @@ public interface ResponseGenerator {
|
||||
TemplateResponse createTemplateUpdateResponse(ResponseView view, VirtualMachineTemplate result);
|
||||
|
||||
List<TemplateResponse> createTemplateResponses(ResponseView view, VirtualMachineTemplate result,
|
||||
Long zoneId, boolean readyOnly);
|
||||
Long zoneId, boolean readyOnly);
|
||||
|
||||
List<TemplateResponse> createTemplateResponses(ResponseView view, VirtualMachineTemplate result,
|
||||
List<Long> zoneIds, boolean readyOnly);
|
||||
List<Long> zoneIds, boolean readyOnly);
|
||||
|
||||
List<CapacityResponse> createCapacityResponse(List<? extends Capacity> result, DecimalFormat format);
|
||||
|
||||
TemplatePermissionsResponse createTemplatePermissionsResponse(ResponseView view, List<String> accountNames, Long id);
|
||||
TemplatePermissionsResponse createTemplatePermissionsResponse(ResponseView view, List<String> accountNames,
|
||||
Long id);
|
||||
|
||||
AsyncJobResponse queryJobResult(QueryAsyncJobResultCmd cmd);
|
||||
|
||||
@ -401,7 +407,8 @@ public interface ResponseGenerator {
|
||||
|
||||
Long getSecurityGroupId(String groupName, long accountId);
|
||||
|
||||
List<TemplateResponse> createIsoResponses(ResponseView view, VirtualMachineTemplate iso, Long zoneId, boolean readyOnly);
|
||||
List<TemplateResponse> createIsoResponses(ResponseView view, VirtualMachineTemplate iso, Long zoneId,
|
||||
boolean readyOnly);
|
||||
|
||||
ProjectResponse createProjectResponse(Project project);
|
||||
|
||||
@ -502,13 +509,15 @@ public interface ResponseGenerator {
|
||||
|
||||
GuestOsMappingResponse createGuestOSMappingResponse(GuestOSHypervisor osHypervisor);
|
||||
|
||||
HypervisorGuestOsNamesResponse createHypervisorGuestOSNamesResponse(List<Pair<String, String>> hypervisorGuestOsNames);
|
||||
HypervisorGuestOsNamesResponse createHypervisorGuestOSNamesResponse(
|
||||
List<Pair<String, String>> hypervisorGuestOsNames);
|
||||
|
||||
SnapshotScheduleResponse createSnapshotScheduleResponse(SnapshotSchedule sched);
|
||||
|
||||
UsageRecordResponse createUsageResponse(Usage usageRecord);
|
||||
|
||||
UsageRecordResponse createUsageResponse(Usage usageRecord, Map<String, Set<ResourceTagResponse>> resourceTagResponseMap, boolean oldFormat);
|
||||
UsageRecordResponse createUsageResponse(Usage usageRecord,
|
||||
Map<String, Set<ResourceTagResponse>> resourceTagResponseMap, boolean oldFormat);
|
||||
|
||||
public Map<String, Set<ResourceTagResponse>> getUsageResourceTags();
|
||||
|
||||
@ -520,7 +529,8 @@ public interface ResponseGenerator {
|
||||
|
||||
public NicResponse createNicResponse(Nic result);
|
||||
|
||||
ApplicationLoadBalancerResponse createLoadBalancerContainerReponse(ApplicationLoadBalancerRule lb, Map<Ip, UserVm> lbInstances);
|
||||
ApplicationLoadBalancerResponse createLoadBalancerContainerReponse(ApplicationLoadBalancerRule lb,
|
||||
Map<Ip, UserVm> lbInstances);
|
||||
|
||||
AffinityGroupResponse createAffinityGroupResponse(AffinityGroup group);
|
||||
|
||||
@ -546,9 +556,12 @@ public interface ResponseGenerator {
|
||||
|
||||
ManagementServerResponse createManagementResponse(ManagementServerHost mgmt);
|
||||
|
||||
List<RouterHealthCheckResultResponse> createHealthCheckResponse(VirtualMachine router, List<RouterHealthCheckResult> healthCheckResults);
|
||||
List<RouterHealthCheckResultResponse> createHealthCheckResponse(VirtualMachine router,
|
||||
List<RouterHealthCheckResult> healthCheckResults);
|
||||
|
||||
RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details, List<RollingMaintenanceManager.HostUpdated> hostsUpdated, List<RollingMaintenanceManager.HostSkipped> hostsSkipped);
|
||||
RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details,
|
||||
List<RollingMaintenanceManager.HostUpdated> hostsUpdated,
|
||||
List<RollingMaintenanceManager.HostSkipped> hostsSkipped);
|
||||
|
||||
ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon);
|
||||
|
||||
@ -558,11 +571,14 @@ public interface ResponseGenerator {
|
||||
|
||||
DirectDownloadCertificateResponse createDirectDownloadCertificateResponse(DirectDownloadCertificate certificate);
|
||||
|
||||
List<DirectDownloadCertificateHostStatusResponse> createDirectDownloadCertificateHostMapResponse(List<DirectDownloadCertificateHostMap> hostMappings);
|
||||
List<DirectDownloadCertificateHostStatusResponse> createDirectDownloadCertificateHostMapResponse(
|
||||
List<DirectDownloadCertificateHostMap> hostMappings);
|
||||
|
||||
DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(DirectDownloadManager.HostCertificateStatus status);
|
||||
DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(
|
||||
DirectDownloadManager.HostCertificateStatus status);
|
||||
|
||||
DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, Long hostId, Pair<Boolean, String> result);
|
||||
DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId,
|
||||
Long hostId, Pair<Boolean, String> result);
|
||||
|
||||
FirewallResponse createIpv6FirewallRuleResponse(FirewallRule acl);
|
||||
|
||||
|
||||
@ -0,0 +1,131 @@
|
||||
// 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.
|
||||
package org.apache.cloudstack.api.command.admin.kms;
|
||||
|
||||
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.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.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.VolumeResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.KMSKey;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
@APICommand(name = "migrateVolumesToKMS",
|
||||
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 {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ACCOUNT,
|
||||
type = CommandType.STRING,
|
||||
description = "Migrate volumes for specific account")
|
||||
private String accountName;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = DomainResponse.class,
|
||||
description = "Domain ID")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.VOLUME_IDS,
|
||||
type = CommandType.LIST,
|
||||
collectionType = CommandType.UUID,
|
||||
entityType = VolumeResponse.class,
|
||||
required = true,
|
||||
description = "List of volume IDs to migrate")
|
||||
private List<Long> volumeIds;
|
||||
|
||||
@Parameter(name = ApiConstants.KMS_KEY_ID,
|
||||
required = true,
|
||||
type = CommandType.UUID,
|
||||
entityType = KMSKeyResponse.class,
|
||||
description = "KMS Key ID to use for migrating volumes")
|
||||
private Long kmsKeyId;
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public List<Long> getVolumeIds() {
|
||||
return volumeIds;
|
||||
}
|
||||
|
||||
public Long getKmsKeyId() {
|
||||
return kmsKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
kmsManager.migrateVolumesToKMS(this);
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
|
||||
"Failed to migrate volumes to KMS: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
KMSKey key = _entityMgr.findById(KMSKey.class, kmsKeyId);
|
||||
if (key != null) {
|
||||
return key.getAccountId();
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return com.cloud.event.EventTypes.EVENT_VOLUME_MIGRATE_TO_KMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventDescription() {
|
||||
return "Migrating volumes to KMS key: " + _uuidMgr.getUuid(KMSKey.class, kmsKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.KmsKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return kmsKeyId;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.cloudstack.api.command.user.kms;
|
||||
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
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;
|
||||
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.ProjectResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "createKMSKey",
|
||||
description = "Creates a new KMS key (Key Encryption Key) for encryption",
|
||||
responseObject = KMSKeyResponse.class,
|
||||
since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false)
|
||||
public class CreateKMSKeyCmd extends BaseCmd implements UserCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@Parameter(name = ApiConstants.NAME,
|
||||
required = true,
|
||||
type = CommandType.STRING,
|
||||
description = "Name of the KMS key")
|
||||
private String name;
|
||||
|
||||
@Parameter(name = ApiConstants.DESCRIPTION,
|
||||
type = CommandType.STRING,
|
||||
description = "Description of the KMS key")
|
||||
private String description;
|
||||
|
||||
@Parameter(name = ApiConstants.ZONE_ID,
|
||||
required = true,
|
||||
type = CommandType.UUID,
|
||||
entityType = ZoneResponse.class,
|
||||
description = "Zone ID where the key will be valid")
|
||||
private Long zoneId;
|
||||
|
||||
@Parameter(name = ApiConstants.ACCOUNT,
|
||||
type = CommandType.STRING,
|
||||
description = "Account name (for creating keys for child accounts - requires domain admin or admin)")
|
||||
private String accountName;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = DomainResponse.class,
|
||||
description = "Domain ID (for creating keys for child accounts - requires domain admin or admin)")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.PROJECT_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = ProjectResponse.class,
|
||||
description = "ID of the project to create the KMS key for")
|
||||
private Long projectId;
|
||||
|
||||
@Parameter(name = ApiConstants.KEY_BITS,
|
||||
type = CommandType.INTEGER,
|
||||
description = "Key size in bits: 128, 192, or 256 (default: 256)")
|
||||
private Integer keyBits;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public Integer getKeyBits() {
|
||||
return keyBits != null ? keyBits : 256;
|
||||
}
|
||||
|
||||
public Long getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceAllocationException {
|
||||
try {
|
||||
KMSKeyResponse response = kmsManager.createKMSKey(this);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
|
||||
"Failed to create KMS key: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true);
|
||||
if (accountId != null) {
|
||||
return accountId;
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.KmsKey;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.cloudstack.api.command.user.kms;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
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.BaseAsyncCmd;
|
||||
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.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.KMSKey;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "deleteKMSKey",
|
||||
description = "Deletes a KMS key (only if not in use)",
|
||||
responseObject = SuccessResponse.class,
|
||||
since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false)
|
||||
public class DeleteKMSKeyCmd extends BaseAsyncCmd implements UserCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
required = true,
|
||||
type = CommandType.UUID,
|
||||
entityType = KMSKeyResponse.class,
|
||||
description = "The UUID of the KMS key to delete")
|
||||
private Long id;
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
SuccessResponse response = kmsManager.deleteKMSKey(this);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
|
||||
"Failed to delete KMS key: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
KMSKey key = _entityMgr.findById(KMSKey.class, id);
|
||||
if (key != null) {
|
||||
return key.getAccountId();
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.KmsKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_KMS_KEY_DELETE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventDescription() {
|
||||
return "deleting KMS key: " + getId();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.cloudstack.api.command.user.kms;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||
import org.apache.cloudstack.api.command.user.UserCmd;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "listKMSKeys",
|
||||
description = "Lists KMS keys available to the caller",
|
||||
responseObject = KMSKeyResponse.class,
|
||||
responseView = ResponseView.Restricted,
|
||||
since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false)
|
||||
public class ListKMSKeysCmd extends BaseListProjectAndAccountResourcesCmd implements UserCmd {
|
||||
private static final String s_name = "listkmskeysresponse";
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = KMSKeyResponse.class,
|
||||
description = "List KMS key by UUID")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.ZONE_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = ZoneResponse.class,
|
||||
description = "Filter by zone ID")
|
||||
private Long zoneId;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED,
|
||||
type = CommandType.BOOLEAN,
|
||||
description = "Filter by enabled status")
|
||||
private Boolean enabled;
|
||||
|
||||
@Parameter(name = ApiConstants.HSM_PROFILE_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = HSMProfileResponse.class,
|
||||
description = "Filter by HSM profile ID")
|
||||
private Long hsmProfileId;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Long getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ListResponse<KMSKeyResponse> listResponse = kmsManager.listKMSKeys(this);
|
||||
listResponse.setResponseName(getCommandName());
|
||||
setResponseObject(listResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return s_name;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
// 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.
|
||||
package org.apache.cloudstack.api.command.user.kms;
|
||||
|
||||
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.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.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.KMSKey;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "rotateKMSKey",
|
||||
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},
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false)
|
||||
public class RotateKMSKeyCmd extends BaseAsyncCmd {
|
||||
private static final String s_name = "rotatekmskeyresponse";
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
required = true,
|
||||
type = CommandType.UUID,
|
||||
entityType = KMSKeyResponse.class,
|
||||
description = "KMS Key UUID to rotate")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.KEY_BITS,
|
||||
type = CommandType.INTEGER,
|
||||
description = "Key size for new KEK (default: same as current)")
|
||||
private Integer keyBits;
|
||||
|
||||
@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 Long hsmProfileId;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Integer getKeyBits() {
|
||||
return keyBits;
|
||||
}
|
||||
|
||||
public Long getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
kmsManager.rotateKMSKey(this);
|
||||
SuccessResponse response = new SuccessResponse();
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
|
||||
"Failed to rotate KMS key: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return s_name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
KMSKey key = _entityMgr.findById(KMSKey.class, id);
|
||||
if (key != null) {
|
||||
return key.getAccountId();
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return com.cloud.event.EventTypes.EVENT_KMS_KEY_ROTATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventDescription() {
|
||||
return "Rotating KMS key: " + _uuidMgr.getUuid(KMSKey.class, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.KmsKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getId();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.cloudstack.api.command.user.kms;
|
||||
|
||||
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;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "updateKMSKey",
|
||||
description = "Updates KMS key name, description, or enabled status",
|
||||
responseObject = KMSKeyResponse.class,
|
||||
since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false)
|
||||
public class UpdateKMSKeyCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
required = true,
|
||||
type = CommandType.UUID,
|
||||
entityType = KMSKeyResponse.class,
|
||||
description = "The UUID of the KMS key to update")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.NAME,
|
||||
type = CommandType.STRING,
|
||||
description = "New name for the key")
|
||||
private String name;
|
||||
|
||||
@Parameter(name = ApiConstants.DESCRIPTION,
|
||||
type = CommandType.STRING,
|
||||
description = "New description for the key")
|
||||
private String description;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED,
|
||||
type = CommandType.BOOLEAN,
|
||||
description = "whether the key should be enabled")
|
||||
private Boolean enabled;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
KMSKeyResponse response = kmsManager.updateKMSKey(this);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
|
||||
"Failed to update KMS key: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.KmsKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getId();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.api.command.user.kms.hsm;
|
||||
|
||||
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.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;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.ProjectResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
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 javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
|
||||
@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 CreateHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@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,
|
||||
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)")
|
||||
private Long zoneId;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
|
||||
description = "the domain ID where the HSM profile is available. If specified without an account, "
|
||||
+ "the profile is domain-scoped and usable by all accounts directly in that domain.")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING,
|
||||
description = "the account name of the HSM profile owner. Must be used with domainId. "
|
||||
+ "Leave empty (with a domainId) to create a domain-scoped profile shared by all "
|
||||
+ "accounts in that domain.")
|
||||
private String accountName;
|
||||
|
||||
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class,
|
||||
description = "the ID of the project to add the HSM profile for")
|
||||
private Long projectId;
|
||||
|
||||
@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 isPublic;
|
||||
|
||||
@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,
|
||||
description = "HSM configuration details (protocol specific)")
|
||||
private Map<String, String> details;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
if (StringUtils.isBlank(protocol)) {
|
||||
return "pkcs11";
|
||||
}
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public Boolean getIsPublic() {
|
||||
return isPublic != null && isPublic;
|
||||
}
|
||||
|
||||
public String getVendorName() {
|
||||
return vendorName;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return convertDetailsToMap(details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
|
||||
ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
try {
|
||||
HSMProfile profile = kmsManager.addHSMProfile(this);
|
||||
HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true);
|
||||
if (accountId != null) {
|
||||
return accountId;
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.HsmProfile;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.api.command.user.kms.hsm;
|
||||
|
||||
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.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;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
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 javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "deleteHSMProfile", description = "Deletes an HSM profile", responseObject = SuccessResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
|
||||
authorized = { RoleType.Admin })
|
||||
public class DeleteHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true,
|
||||
description = "the ID of the HSM profile")
|
||||
private Long id;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
|
||||
ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
try {
|
||||
boolean result = kmsManager.deleteHSMProfile(this);
|
||||
if (result) {
|
||||
SuccessResponse response = new SuccessResponse(getCommandName());
|
||||
setResponseObject(response);
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete HSM profile");
|
||||
}
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
HSMProfile profile = _entityMgr.findById(HSMProfile.class, id);
|
||||
if (profile != null && profile.getAccountId() > 0) {
|
||||
return profile.getAccountId();
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.HsmProfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getId();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.api.command.user.kms.hsm;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "listHSMProfiles", description = "Lists HSM profiles", responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class ListHSMProfilesCmd extends BaseListProjectAndAccountResourcesCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@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;
|
||||
|
||||
@Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, description = "the protocol of the HSM profile")
|
||||
private String protocol;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "list only enabled profiles")
|
||||
private Boolean enabled;
|
||||
|
||||
@Parameter(name = ApiConstants.IS_PUBLIC,
|
||||
type = CommandType.BOOLEAN,
|
||||
description = "when true, non-admin users see only system (global) profiles")
|
||||
private Boolean isPublic;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Boolean getIsPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ListResponse<HSMProfileResponse> response = kmsManager.listHSMProfiles(this);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.api.command.user.kms.hsm;
|
||||
|
||||
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.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;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.HSMProfileResponse;
|
||||
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 javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "updateHSMProfile", description = "Updates an HSM profile",
|
||||
responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0",
|
||||
authorized = { RoleType.Admin })
|
||||
public class UpdateHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true,
|
||||
description = "the ID of the HSM profile")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the HSM profile")
|
||||
private String name;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN,
|
||||
description = "whether the HSM profile is enabled")
|
||||
private Boolean enabled;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
|
||||
ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
try {
|
||||
HSMProfile profile = kmsManager.updateHSMProfile(this);
|
||||
HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
} catch (KMSException e) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
HSMProfile profile = _entityMgr.findById(HSMProfile.class, id);
|
||||
if (profile != null && profile.getAccountId() > 0) {
|
||||
return profile.getAccountId();
|
||||
}
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.HsmProfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getId();
|
||||
}
|
||||
}
|
||||
@ -40,12 +40,14 @@ import org.apache.cloudstack.api.command.user.UserCmd;
|
||||
import org.apache.cloudstack.api.response.DiskOfferingResponse;
|
||||
import org.apache.cloudstack.api.response.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.HostResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.NetworkResponse;
|
||||
import org.apache.cloudstack.api.response.ProjectResponse;
|
||||
import org.apache.cloudstack.api.response.SecurityGroupResponse;
|
||||
import org.apache.cloudstack.api.response.UserDataResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.kms.KMSKey;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
@ -126,11 +128,19 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
|
||||
since = "4.4")
|
||||
private Long rootdisksize;
|
||||
|
||||
@ACL
|
||||
@Parameter(name = ApiConstants.ROOT_DISK_KMS_KEY_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = KMSKeyResponse.class,
|
||||
description = "ID of the KMS Key to use for root disk encryption",
|
||||
since = "4.23.0")
|
||||
private Long rootDiskKmsKeyId;
|
||||
|
||||
@Parameter(name = ApiConstants.DATADISKS_DETAILS,
|
||||
type = CommandType.MAP,
|
||||
since = "4.21.0",
|
||||
description = "Disk offering details for creating multiple data volumes. Mutually exclusive with diskOfferingId." +
|
||||
" Example: datadisksdetails[0].diskofferingid=a2a73a84-19db-4852-8930-dfddef053341&datadisksdetails[0].size=10&datadisksdetails[0].miniops=100&datadisksdetails[0].maxiops=200")
|
||||
" Example: datadisksdetails[0].diskofferingid=a2a73a84-19db-4852-8930-dfddef053341&datadisksdetails[0].size=10&datadisksdetails[0].miniops=100&datadisksdetails[0].maxiops=200&datadisksdetails[0].kmskeyid=<uuid>")
|
||||
private Map dataDisksDetails;
|
||||
|
||||
@Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "an optional group for the virtual machine")
|
||||
@ -300,6 +310,10 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
|
||||
return diskOfferingId;
|
||||
}
|
||||
|
||||
public Long getRootDiskKmsKeyId() {
|
||||
return rootDiskKmsKeyId;
|
||||
}
|
||||
|
||||
public String getDeploymentPlanner() {
|
||||
return deploymentPlanner;
|
||||
}
|
||||
@ -581,7 +595,19 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
|
||||
minIops = Long.parseLong(dataDisk.get(ApiConstants.MIN_IOPS));
|
||||
maxIops = Long.parseLong(dataDisk.get(ApiConstants.MAX_IOPS));
|
||||
}
|
||||
VmDiskInfo vmDiskInfo = new VmDiskInfo(diskOffering, size, minIops, maxIops, deviceId);
|
||||
|
||||
// Extract KMS key ID if provided
|
||||
Long kmsKeyId = null;
|
||||
String kmsKeyUuid = dataDisk.get(ApiConstants.KMS_KEY_ID);
|
||||
if (kmsKeyUuid != null) {
|
||||
KMSKey kmsKey = _entityMgr.findByUuid(org.apache.cloudstack.kms.KMSKey.class, kmsKeyUuid);
|
||||
if (kmsKey == null) {
|
||||
throw new InvalidParameterValueException("Unable to find KMS key " + kmsKeyUuid);
|
||||
}
|
||||
kmsKeyId = kmsKey.getId();
|
||||
}
|
||||
|
||||
VmDiskInfo vmDiskInfo = new VmDiskInfo(diskOffering, size, minIops, maxIops, deviceId, kmsKeyId);
|
||||
vmDiskInfoList.add(vmDiskInfo);
|
||||
}
|
||||
this.dataDiskInfoList = vmDiskInfoList;
|
||||
|
||||
@ -30,6 +30,7 @@ import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.command.user.UserCmd;
|
||||
import org.apache.cloudstack.api.response.DiskOfferingResponse;
|
||||
import org.apache.cloudstack.api.response.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.ProjectResponse;
|
||||
import org.apache.cloudstack.api.response.SnapshotResponse;
|
||||
import org.apache.cloudstack.api.response.StoragePoolResponse;
|
||||
@ -110,6 +111,13 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC
|
||||
description = "The ID of the Instance; to be used with snapshot Id, Instance to which the volume gets attached after creation")
|
||||
private Long virtualMachineId;
|
||||
|
||||
@Parameter(name = ApiConstants.KMS_KEY_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = KMSKeyResponse.class,
|
||||
description = "ID of the KMS Key for volume encryption (required if encryption enabled for zone)",
|
||||
since = "4.23.0")
|
||||
private Long kmsKeyId;
|
||||
|
||||
@Parameter(name = ApiConstants.STORAGE_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = StoragePoolResponse.class,
|
||||
@ -190,6 +198,10 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC
|
||||
return virtualMachineId;
|
||||
}
|
||||
|
||||
public Long getKmsKeyId() {
|
||||
return kmsKeyId;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@ -29,6 +29,7 @@ import org.apache.cloudstack.api.command.user.UserCmd;
|
||||
import org.apache.cloudstack.api.response.ClusterResponse;
|
||||
import org.apache.cloudstack.api.response.DiskOfferingResponse;
|
||||
import org.apache.cloudstack.api.response.HostResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.PodResponse;
|
||||
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
|
||||
@ -90,6 +91,9 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme
|
||||
@Parameter(name = ApiConstants.DISK_OFFERING_ID, type = CommandType.UUID, entityType = DiskOfferingResponse.class, description = "List volumes by disk offering", since = "4.4")
|
||||
private Long diskOfferingId;
|
||||
|
||||
@Parameter(name = ApiConstants.KMS_KEY_ID, type = CommandType.UUID, entityType = KMSKeyResponse.class, description = "List volumes by KMS Key", since = "4.23")
|
||||
private Long kmsKeyId;
|
||||
|
||||
@Parameter(name = ApiConstants.DISPLAY_VOLUME, type = CommandType.BOOLEAN, description = "List resources by display flag; only ROOT admin is eligible to pass this parameter", since = "4.4", authorized = {
|
||||
RoleType.Admin})
|
||||
private Boolean display;
|
||||
@ -136,6 +140,10 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme
|
||||
return diskOfferingId;
|
||||
}
|
||||
|
||||
public Long getKmsKeyId() {
|
||||
return kmsKeyId;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@ -0,0 +1,254 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.api.response;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.kms.HSMProfile;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@EntityReference(value = HSMProfile.class)
|
||||
public class HSMProfileResponse extends BaseResponse implements ControlledViewEntityResponse {
|
||||
@SerializedName(ApiConstants.ID)
|
||||
@Param(description = "the ID of the HSM profile")
|
||||
private String id;
|
||||
|
||||
@SerializedName(ApiConstants.NAME)
|
||||
@Param(description = "the name of the HSM profile")
|
||||
private String name;
|
||||
|
||||
@SerializedName(ApiConstants.PROTOCOL)
|
||||
@Param(description = "the protocol of the HSM profile")
|
||||
private String protocol;
|
||||
|
||||
@SerializedName(ApiConstants.ACCOUNT_ID)
|
||||
@Param(description = "the account ID of the HSM profile owner")
|
||||
private String accountId;
|
||||
|
||||
@SerializedName(ApiConstants.ACCOUNT)
|
||||
@Param(description = "the account name of the HSM profile owner")
|
||||
private String accountName;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN_ID)
|
||||
@Param(description = "the domain ID of the HSM profile owner")
|
||||
private String domainId;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN)
|
||||
@Param(description = "the domain name of the HSM profile owner")
|
||||
private String domainName;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN_PATH)
|
||||
@Param(description = "the domain path of the HSM profile owner")
|
||||
private String domainPath;
|
||||
|
||||
@SerializedName(ApiConstants.PROJECT_ID)
|
||||
@Param(description = "the project ID of the HSM profile owner")
|
||||
private String projectId;
|
||||
|
||||
@SerializedName(ApiConstants.PROJECT)
|
||||
@Param(description = "the project name of the HSM profile owner")
|
||||
private String projectName;
|
||||
|
||||
@SerializedName(ApiConstants.ZONE_ID)
|
||||
@Param(description = "the zone ID where the HSM profile is available")
|
||||
private String zoneId;
|
||||
|
||||
@SerializedName(ApiConstants.ZONE_NAME)
|
||||
@Param(description = "the zone name where the HSM profile is available")
|
||||
private String zoneName;
|
||||
|
||||
@SerializedName("vendor")
|
||||
@Param(description = "the vendor name of the HSM profile")
|
||||
private String vendorName;
|
||||
|
||||
@SerializedName(ApiConstants.STATE)
|
||||
@Param(description = "the state of the HSM profile")
|
||||
private String state;
|
||||
|
||||
@SerializedName(ApiConstants.ENABLED)
|
||||
@Param(description = "whether the HSM profile is enabled")
|
||||
private Boolean enabled;
|
||||
|
||||
@SerializedName(ApiConstants.IS_PUBLIC)
|
||||
@Param(description = "whether this is a system HSM profile available to all users globally")
|
||||
private Boolean isPublic;
|
||||
|
||||
@SerializedName(ApiConstants.CREATED)
|
||||
@Param(description = "the date the HSM profile was created")
|
||||
private Date created;
|
||||
|
||||
@SerializedName(ApiConstants.DETAILS)
|
||||
@Param(description = "HSM configuration details (sensitive values are encrypted)")
|
||||
private Map<String, String> details;
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public void setAccountId(String accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccountName(String accountName) {
|
||||
this.accountName = accountName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDomainId(String domainId) {
|
||||
this.domainId = domainId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDomainName(String domainName) {
|
||||
this.domainName = domainName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDomainPath(String domainPath) {
|
||||
this.domainPath = domainPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectId(String projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectName(String projectName) {
|
||||
this.projectName = projectName;
|
||||
}
|
||||
|
||||
public void setZoneId(String zoneId) {
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public void setZoneName(String zoneName) {
|
||||
this.zoneName = zoneName;
|
||||
}
|
||||
|
||||
public void setVendorName(String vendorName) {
|
||||
this.vendorName = vendorName;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setIsPublic(Boolean isPublic) {
|
||||
this.isPublic = isPublic;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public void setDetails(Map<String, String> details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public String getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public String getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public String getDomainName() {
|
||||
return domainName;
|
||||
}
|
||||
|
||||
public String getDomainPath() {
|
||||
return domainPath;
|
||||
}
|
||||
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public String getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
|
||||
public String getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public String getZoneName() {
|
||||
return zoneName;
|
||||
}
|
||||
|
||||
public String getVendorName() {
|
||||
return vendorName;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Boolean getPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.cloudstack.api.response;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.kms.KMSKey;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@EntityReference(value = KMSKey.class)
|
||||
public class KMSKeyResponse extends BaseResponse implements ControlledViewEntityResponse {
|
||||
|
||||
@SerializedName(ApiConstants.ID)
|
||||
@Param(description = "the UUID of the key")
|
||||
private String id;
|
||||
|
||||
@SerializedName(ApiConstants.NAME)
|
||||
@Param(description = "the name of the key")
|
||||
private String name;
|
||||
|
||||
@SerializedName(ApiConstants.DESCRIPTION)
|
||||
@Param(description = "the description of the key")
|
||||
private String description;
|
||||
|
||||
@SerializedName(ApiConstants.ACCOUNT)
|
||||
@Param(description = "the account owning the key")
|
||||
private String accountName;
|
||||
|
||||
@SerializedName(ApiConstants.ACCOUNT_ID)
|
||||
@Param(description = "the account ID owning the key")
|
||||
private String accountId;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN_ID)
|
||||
@Param(description = "the domain ID of the key")
|
||||
private String domainId;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN)
|
||||
@Param(description = "the domain name of the key")
|
||||
private String domainName;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN_PATH)
|
||||
@Param(description = "the domain path of the key")
|
||||
private String domainPath;
|
||||
|
||||
@SerializedName(ApiConstants.ZONE_ID)
|
||||
@Param(description = "the zone ID where the key is valid")
|
||||
private String zoneId;
|
||||
|
||||
@SerializedName(ApiConstants.ZONE_NAME)
|
||||
@Param(description = "the zone name where the key is valid")
|
||||
private String zoneName;
|
||||
|
||||
@SerializedName(ApiConstants.HSM_PROFILE_ID)
|
||||
@Param(description = "the zone ID where the key is valid")
|
||||
private String hsmProfileId;
|
||||
|
||||
@SerializedName(ApiConstants.HSM_PROFILE)
|
||||
@Param(description = "the zone name where the key is valid")
|
||||
private String hsmProfileName;
|
||||
|
||||
@SerializedName(ApiConstants.ALGORITHM)
|
||||
@Param(description = "the encryption algorithm")
|
||||
private String algorithm;
|
||||
|
||||
@SerializedName(ApiConstants.KEY_BITS)
|
||||
@Param(description = "the key size in bits")
|
||||
private Integer keyBits;
|
||||
|
||||
@SerializedName(ApiConstants.VERSION)
|
||||
@Param(description = "the key size in bits")
|
||||
private Integer version;
|
||||
|
||||
@SerializedName(ApiConstants.ENABLED)
|
||||
@Param(description = "whether the key is enabled")
|
||||
private Boolean enabled;
|
||||
|
||||
@SerializedName(ApiConstants.CREATED)
|
||||
@Param(description = "the creation timestamp")
|
||||
private Date created;
|
||||
|
||||
@SerializedName(ApiConstants.PROJECT_ID)
|
||||
@Param(description = "the project ID of the key")
|
||||
private String projectId;
|
||||
|
||||
@SerializedName(ApiConstants.PROJECT)
|
||||
@Param(description = "the project name of the key")
|
||||
private String projectName;
|
||||
|
||||
@SerializedName(ApiConstants.KEK_LABEL)
|
||||
@Param(description = "the provider-specific KEK label (admin only)", authorized = { RoleType.Admin })
|
||||
private String kekLabel;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccountName(String accountName) {
|
||||
this.accountName = accountName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectId(String projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectName(String projectName) {
|
||||
this.projectName = projectName;
|
||||
}
|
||||
|
||||
public String getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public void setAccountId(String accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
public String getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDomainId(String domainId) {
|
||||
this.domainId = domainId;
|
||||
}
|
||||
|
||||
public String getDomainName() {
|
||||
return domainName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDomainName(String domainName) {
|
||||
this.domainName = domainName;
|
||||
}
|
||||
|
||||
public String getDomainPath() {
|
||||
return domainPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDomainPath(String domainPath) {
|
||||
this.domainPath = domainPath;
|
||||
}
|
||||
|
||||
public String getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public void setZoneId(String zoneId) {
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public String getZoneName() {
|
||||
return zoneName;
|
||||
}
|
||||
|
||||
public void setZoneName(String zoneName) {
|
||||
this.zoneName = zoneName;
|
||||
}
|
||||
|
||||
public String getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
||||
public void setHsmProfileId(String hsmProfileId) {
|
||||
this.hsmProfileId = hsmProfileId;
|
||||
}
|
||||
|
||||
public String getHsmProfileName() {
|
||||
return hsmProfileName;
|
||||
}
|
||||
|
||||
public void setHsmProfileName(String hsmProfileName) {
|
||||
this.hsmProfileName = hsmProfileName;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public Integer getKeyBits() {
|
||||
return keyBits;
|
||||
}
|
||||
|
||||
public void setKeyBits(Integer keyBits) {
|
||||
this.keyBits = keyBits;
|
||||
}
|
||||
|
||||
public Integer getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Integer version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public String getKekLabel() {
|
||||
return kekLabel;
|
||||
}
|
||||
|
||||
public void setKekLabel(String kekLabel) {
|
||||
this.kekLabel = kekLabel;
|
||||
}
|
||||
}
|
||||
@ -309,6 +309,18 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
||||
@Param(description = "the format of the disk encryption if applicable", since = "4.19.1")
|
||||
private String encryptionFormat;
|
||||
|
||||
@SerializedName(ApiConstants.KMS_KEY)
|
||||
@Param(description = "KMS key name of the volume", since = "4.23.0")
|
||||
private String kmsKey;
|
||||
|
||||
@SerializedName(ApiConstants.KMS_KEY_ID)
|
||||
@Param(description = "KMS key id of the volume", since = "4.23.0")
|
||||
private String kmsKeyId;
|
||||
|
||||
@SerializedName(ApiConstants.KMS_KEY_VERSION)
|
||||
@Param(description = "Version number of the KMS key used for disk encryption if applicable", since = "4.23.0")
|
||||
private Integer kmsKeyVersion;
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
@ -868,7 +880,35 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
||||
this.volumeRepairResult = volumeRepairResult;
|
||||
}
|
||||
|
||||
public String getEncryptionFormat() {
|
||||
return encryptionFormat;
|
||||
}
|
||||
|
||||
public void setEncryptionFormat(String encryptionFormat) {
|
||||
this.encryptionFormat = encryptionFormat;
|
||||
}
|
||||
|
||||
public String getKmsKey() {
|
||||
return kmsKey;
|
||||
}
|
||||
|
||||
public void setKmsKey(String kmsKey) {
|
||||
this.kmsKey = kmsKey;
|
||||
}
|
||||
|
||||
public String getKmsKeyId() {
|
||||
return kmsKeyId;
|
||||
}
|
||||
|
||||
public void setKmsKeyId(String kmsKeyId) {
|
||||
this.kmsKeyId = kmsKeyId;
|
||||
}
|
||||
|
||||
public Integer getKmsKeyVersion() {
|
||||
return kmsKeyVersion;
|
||||
}
|
||||
|
||||
public void setKmsKeyVersion(Integer kmsKeyVersion) {
|
||||
this.kmsKeyVersion = kmsKeyVersion;
|
||||
}
|
||||
}
|
||||
|
||||
46
api/src/main/java/org/apache/cloudstack/kms/HSMProfile.java
Normal file
46
api/src/main/java/org/apache/cloudstack/kms/HSMProfile.java
Normal file
@ -0,0 +1,46 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms;
|
||||
|
||||
import org.apache.cloudstack.acl.ControlledEntity;
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface HSMProfile extends Identity, InternalIdentity, ControlledEntity {
|
||||
String getName();
|
||||
|
||||
String getProtocol();
|
||||
|
||||
long getAccountId();
|
||||
|
||||
long getDomainId();
|
||||
|
||||
Long getZoneId();
|
||||
|
||||
String getVendorName();
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
boolean getIsPublic();
|
||||
|
||||
Date getCreated();
|
||||
|
||||
Date getRemoved();
|
||||
}
|
||||
60
api/src/main/java/org/apache/cloudstack/kms/KMSKey.java
Normal file
60
api/src/main/java/org/apache/cloudstack/kms/KMSKey.java
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.cloudstack.kms;
|
||||
|
||||
import org.apache.cloudstack.acl.ControlledEntity;
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* KMS Key (Key Encryption Key) metadata.
|
||||
* Represents a KEK that can be used to wrap/unwrap Data Encryption Keys (DEKs).
|
||||
* KEKs are account-scoped and used for envelope encryption.
|
||||
*/
|
||||
public interface KMSKey extends Identity, InternalIdentity, ControlledEntity {
|
||||
|
||||
String getName();
|
||||
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* Provider-specific KEK label/ID (internal identifier used by the KMS provider)
|
||||
*/
|
||||
String getKekLabel();
|
||||
|
||||
KeyPurpose getPurpose();
|
||||
|
||||
Long getZoneId();
|
||||
|
||||
String getAlgorithm();
|
||||
|
||||
Integer getKeyBits();
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
Date getCreated();
|
||||
|
||||
Date getRemoved();
|
||||
|
||||
Long getHsmProfileId();
|
||||
}
|
||||
292
api/src/main/java/org/apache/cloudstack/kms/KMSManager.java
Normal file
292
api/src/main/java/org/apache/cloudstack/kms/KMSManager.java
Normal file
@ -0,0 +1,292 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms;
|
||||
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.utils.component.Manager;
|
||||
import org.apache.cloudstack.api.command.admin.kms.MigrateVolumesToKMSCmd;
|
||||
import org.apache.cloudstack.api.command.user.kms.RotateKMSKeyCmd;
|
||||
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.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.response.HSMProfileResponse;
|
||||
import org.apache.cloudstack.api.response.KMSKeyResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.framework.kms.KMSProvider;
|
||||
import org.apache.cloudstack.framework.kms.WrappedKey;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface KMSManager extends Manager, Configurable {
|
||||
|
||||
ConfigKey<Integer> KMSDekSizeBits = new ConfigKey<>(
|
||||
"Advanced",
|
||||
Integer.class,
|
||||
"kms.dek.size.bits",
|
||||
"256",
|
||||
"The size of Data Encryption Keys (DEK) in bits (128, 192, or 256)",
|
||||
true,
|
||||
ConfigKey.Scope.Global
|
||||
);
|
||||
|
||||
ConfigKey<Integer> KMSRetryCount = new ConfigKey<>(
|
||||
"Advanced",
|
||||
Integer.class,
|
||||
"kms.retry.count",
|
||||
"3",
|
||||
"Number of retry attempts for transient KMS failures",
|
||||
true,
|
||||
ConfigKey.Scope.Global
|
||||
);
|
||||
|
||||
ConfigKey<Integer> KMSRetryDelayMs = new ConfigKey<>(
|
||||
"Advanced",
|
||||
Integer.class,
|
||||
"kms.retry.delay.ms",
|
||||
"1000",
|
||||
"Delay in milliseconds between KMS retry attempts (exponential backoff)",
|
||||
true,
|
||||
ConfigKey.Scope.Global
|
||||
);
|
||||
|
||||
ConfigKey<Integer> KMSOperationTimeoutSec = new ConfigKey<>(
|
||||
"Advanced",
|
||||
Integer.class,
|
||||
"kms.operation.timeout.sec",
|
||||
"30",
|
||||
"Timeout in seconds for KMS cryptographic operations",
|
||||
true,
|
||||
ConfigKey.Scope.Global
|
||||
);
|
||||
|
||||
ConfigKey<Integer> KMSRewrapBatchSize = new ConfigKey<>(
|
||||
"Advanced",
|
||||
Integer.class,
|
||||
"kms.rewrap.batch.size",
|
||||
"50",
|
||||
"Number of wrapped keys to rewrap per batch in background job",
|
||||
true,
|
||||
ConfigKey.Scope.Global
|
||||
);
|
||||
|
||||
ConfigKey<Long> KMSRewrapIntervalMs = new ConfigKey<>(
|
||||
"Advanced",
|
||||
Long.class,
|
||||
"kms.rewrap.interval.ms",
|
||||
"300000",
|
||||
"Interval in milliseconds between background rewrap job executions (default: 5 minutes)",
|
||||
true,
|
||||
ConfigKey.Scope.Global
|
||||
);
|
||||
|
||||
ConfigKey<Integer> KMSOperationPoolCoreSize = new ConfigKey<>(
|
||||
"Advanced",
|
||||
Integer.class,
|
||||
"kms.operation.pool.core.size",
|
||||
"2",
|
||||
"Minimum number of threads kept alive for KMS cryptographic operations",
|
||||
true,
|
||||
ConfigKey.Scope.Global
|
||||
);
|
||||
|
||||
ConfigKey<Integer> KMSOperationPoolMaxSize = new ConfigKey<>(
|
||||
"Advanced",
|
||||
Integer.class,
|
||||
"kms.operation.pool.max.size",
|
||||
"100",
|
||||
"Maximum number of concurrent threads for KMS cryptographic operations. " +
|
||||
"Set this to match the concurrency limit of your HSM appliance or external KMS provider.",
|
||||
true,
|
||||
ConfigKey.Scope.Global
|
||||
);
|
||||
|
||||
/**
|
||||
* List all registered KMS providers
|
||||
*
|
||||
* @return list of available providers
|
||||
*/
|
||||
List<? extends KMSProvider> listKMSProviders();
|
||||
|
||||
/**
|
||||
* Get a specific KMS provider by name
|
||||
*
|
||||
* @param name provider name
|
||||
* @return the provider, or null if not found
|
||||
*/
|
||||
KMSProvider getKMSProvider(String name);
|
||||
|
||||
/**
|
||||
* Check if caller has permission to use a KMS key
|
||||
*
|
||||
* @param callerAccountId the caller's account ID
|
||||
* @param key the KMS key
|
||||
* @return true if caller has permission
|
||||
*/
|
||||
boolean hasPermission(Long callerAccountId, KMSKey key);
|
||||
|
||||
/**
|
||||
* Validates that the KMS key can be used for volume encryption: key exists, not deleted,
|
||||
* owner account matches the volume owner, key state is Enabled, and key purpose is VOLUME_ENCRYPTION.
|
||||
* No-op if kmsKeyId is null.
|
||||
*
|
||||
* @param owner the account that will own the volume
|
||||
* @param kmsKeyId the KMS key database ID
|
||||
* @param zoneId the zone ID of the target resource (volume/VM)
|
||||
* @throws InvalidParameterValueException if key not found, disabled, wrong purpose, zone mismatch, or account mismatch
|
||||
*/
|
||||
void checkKmsKeyForVolumeEncryption(Account owner, Long kmsKeyId, Long zoneId);
|
||||
|
||||
/**
|
||||
* Unwrap a DEK by wrapped key ID, trying multiple KEK versions if needed
|
||||
*
|
||||
* @param wrappedKeyId the wrapped key database ID
|
||||
* @return plaintext DEK (caller must zeroize!)
|
||||
* @throws KMSException if unwrap fails
|
||||
*/
|
||||
byte[] unwrapKey(Long wrappedKeyId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Generate and wrap a DEK using a specific KMS key UUID
|
||||
*
|
||||
* @param kmsKey the KMS key
|
||||
* @param callerAccountId the caller's account ID
|
||||
* @return wrapped key ready for database storage
|
||||
* @throws KMSException if operation fails
|
||||
*/
|
||||
WrappedKey generateVolumeKeyWithKek(KMSKey kmsKey, Long callerAccountId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Create a KMS key and return the response object.
|
||||
* Handles validation, account resolution, and permission checks.
|
||||
*
|
||||
* @param cmd the create command with all parameters
|
||||
* @return KMSKeyResponse
|
||||
* @throws KMSException if creation fails
|
||||
*/
|
||||
KMSKeyResponse createKMSKey(CreateKMSKeyCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* List KMS keys and return the response object.
|
||||
* Handles validation and permission checks.
|
||||
*
|
||||
* @param cmd the list command with all parameters
|
||||
* @return ListResponse with KMSKeyResponse objects
|
||||
*/
|
||||
ListResponse<KMSKeyResponse> listKMSKeys(ListKMSKeysCmd cmd);
|
||||
|
||||
/**
|
||||
* Update a KMS key and return the response object.
|
||||
* Handles validation and permission checks.
|
||||
*
|
||||
* @param cmd the update command with all parameters
|
||||
* @return KMSKeyResponse
|
||||
* @throws KMSException if update fails
|
||||
*/
|
||||
KMSKeyResponse updateKMSKey(UpdateKMSKeyCmd cmd) throws KMSException;
|
||||
|
||||
boolean deleteKMSWrappedKey(Volume vol) throws KMSException;
|
||||
|
||||
/**
|
||||
* Delete a KMS key and return the response object.
|
||||
* Handles validation and permission checks.
|
||||
*
|
||||
* @param cmd the delete command with all parameters
|
||||
* @return SuccessResponse
|
||||
* @throws KMSException if deletion fails
|
||||
*/
|
||||
SuccessResponse deleteKMSKey(DeleteKMSKeyCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* Rotate KEK by creating new version and scheduling gradual re-encryption
|
||||
*
|
||||
* @param cmd the rotate command with all parameters
|
||||
* @return New KEK version UUID
|
||||
* @throws KMSException if rotation fails
|
||||
*/
|
||||
String rotateKMSKey(RotateKMSKeyCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* Migrate passphrase-based volumes to KMS encryption
|
||||
*
|
||||
* @param cmd the migrate command with all parameters
|
||||
* @return Number of volumes successfully migrated
|
||||
* @throws KMSException if migration fails
|
||||
*/
|
||||
int migrateVolumesToKMS(MigrateVolumesToKMSCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* Delete all KMS keys owned by an account (called during account cleanup)
|
||||
*
|
||||
* @param accountId the account ID
|
||||
* @return true if all keys were successfully deleted
|
||||
*/
|
||||
boolean deleteKMSKeysByAccountId(Long accountId);
|
||||
|
||||
/**
|
||||
* Add a new HSM profile
|
||||
*
|
||||
* @param cmd the add command
|
||||
* @return the created HSM profile
|
||||
* @throws KMSException if addition fails
|
||||
*/
|
||||
HSMProfile addHSMProfile(CreateHSMProfileCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* List HSM profiles
|
||||
*
|
||||
* @param cmd the list command
|
||||
* @return list of HSM profiles
|
||||
*/
|
||||
ListResponse<HSMProfileResponse> listHSMProfiles(ListHSMProfilesCmd cmd);
|
||||
|
||||
/**
|
||||
* Delete an HSM profile
|
||||
*
|
||||
* @param cmd the delete command
|
||||
* @return true if deletion was successful
|
||||
* @throws KMSException if deletion fails
|
||||
*/
|
||||
boolean deleteHSMProfile(DeleteHSMProfileCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* Update an HSM profile
|
||||
*
|
||||
* @param cmd the update command
|
||||
* @return the updated HSM profile
|
||||
* @throws KMSException if update fails
|
||||
*/
|
||||
HSMProfile updateHSMProfile(UpdateHSMProfileCmd cmd) throws KMSException;
|
||||
|
||||
/**
|
||||
* Create a response object for an HSM profile
|
||||
*
|
||||
* @param profile the HSM profile
|
||||
* @return the response object
|
||||
*/
|
||||
HSMProfileResponse createHSMProfileResponse(HSMProfile profile);
|
||||
}
|
||||
@ -256,6 +256,16 @@
|
||||
<artifactId>cloud-plugin-metrics</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<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>
|
||||
|
||||
@ -366,4 +366,7 @@
|
||||
|
||||
<bean id="sharedFSProvidersRegistry" class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
|
||||
</bean>
|
||||
|
||||
<bean id="kmsProvidersRegistry" class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
|
||||
</bean>
|
||||
</beans>
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
name=kms
|
||||
parent=core
|
||||
@ -0,0 +1,29 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
|
||||
>
|
||||
<bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
|
||||
<property name="registry" ref="kmsProvidersRegistry" />
|
||||
<property name="typeClass" value="org.apache.cloudstack.framework.kms.KMSProvider" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
@ -120,7 +120,7 @@ public interface VolumeOrchestrationService {
|
||||
void destroyVolume(Volume volume);
|
||||
|
||||
DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template,
|
||||
Account owner, Long deviceId, boolean incrementResourceCount);
|
||||
Account owner, Long deviceId, Long kmsKeyId, boolean incrementResourceCount);
|
||||
|
||||
VolumeInfo createVolumeOnPrimaryStorage(VirtualMachine vm, VolumeInfo volume, HypervisorType rootDiskHyperType, StoragePool storagePool) throws NoTransitionException;
|
||||
|
||||
@ -150,7 +150,7 @@ public interface VolumeOrchestrationService {
|
||||
* Allocate a volume or multiple volumes in case of template is registered with the 'deploy-as-is' option, allowing multiple disks
|
||||
*/
|
||||
List<DiskProfile> allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,
|
||||
Account owner, Volume volume, Snapshot snapshot);
|
||||
Account owner, Long kmsKeyId, Volume volume, Snapshot snapshot);
|
||||
|
||||
String getVmNameFromVolumeId(long volumeId);
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ public interface OrchestrationService {
|
||||
@QueryParam("network-nic-map") Map<String, List<NicProfile>> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan,
|
||||
@QueryParam("root-disk-size") Long rootDiskSize, @QueryParam("extra-dhcp-option-map") Map<String, Map<Integer, String>> extraDhcpOptionMap,
|
||||
@QueryParam("datadisktemplate-diskoffering-map") Map<Long, DiskOffering> datadiskTemplateToDiskOfferingMap, @QueryParam("disk-offering-id") Long diskOfferingId,
|
||||
@QueryParam("root-disk-offering-id") Long rootDiskOfferingId, List<VmDiskInfo> dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException;
|
||||
@QueryParam("root-disk-offering-id") Long rootDiskOfferingId, @QueryParam("root-disk-kms-key-id") Long rootDiskKmsKeyId, List<VmDiskInfo> dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException;
|
||||
|
||||
@POST
|
||||
VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("iso-id") String isoId,
|
||||
@ -80,7 +80,7 @@ public interface OrchestrationService {
|
||||
@QueryParam("compute-tags") List<String> computeTags, @QueryParam("root-disk-tags") List<String> rootDiskTags,
|
||||
@QueryParam("network-nic-map") Map<String, List<NicProfile>> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan,
|
||||
@QueryParam("extra-dhcp-option-map") Map<String, Map<Integer, String>> extraDhcpOptionMap, @QueryParam("disk-offering-id") Long diskOfferingId,
|
||||
@QueryParam("data-disks-offering-info") List<VmDiskInfo> dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException;
|
||||
@QueryParam("root-disk-kms-key-id") Long rootDiskKmsKeyId, @QueryParam("data-disks-offering-info") List<VmDiskInfo> dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException;
|
||||
|
||||
@POST
|
||||
NetworkEntity createNetwork(String id, String name, String domainName, String cidr, String gateway);
|
||||
|
||||
@ -601,7 +601,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
Long deviceId = dataDiskDeviceIds.get(index++);
|
||||
String volumeName = deviceId == null ? "DATA-" + persistedVm.getId() : "DATA-" + persistedVm.getId() + "-" + String.valueOf(deviceId);
|
||||
volumeMgr.allocateRawVolume(Type.DATADISK, volumeName, dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(),
|
||||
dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId, true);
|
||||
dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId, dataDiskOfferingInfo.getKmsKeyId(), true);
|
||||
}
|
||||
}
|
||||
if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) {
|
||||
@ -611,7 +611,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024);
|
||||
VMTemplateVO dataDiskTemplate = _templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey());
|
||||
volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf( diskNumber), diskOffering, diskOfferingSize, null, null,
|
||||
persistedVm, dataDiskTemplate, owner, diskNumber, true);
|
||||
persistedVm, dataDiskTemplate, owner, diskNumber, null, true);
|
||||
diskNumber++;
|
||||
}
|
||||
}
|
||||
@ -641,12 +641,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
String rootVolumeName = String.format("ROOT-%s", vm.getId());
|
||||
if (template.getFormat() == ImageFormat.ISO) {
|
||||
volumeMgr.allocateRawVolume(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(),
|
||||
rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null, true);
|
||||
rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null, rootDiskOfferingInfo.getKmsKeyId(), true);
|
||||
} else if (Arrays.asList(ImageFormat.BAREMETAL, ImageFormat.EXTERNAL).contains(template.getFormat())) {
|
||||
logger.debug("{} has format [{}]. Skipping ROOT volume [{}] allocation.", template, template.getFormat(), rootVolumeName);
|
||||
} else {
|
||||
volumeMgr.allocateTemplatedVolumes(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskSizeFinal,
|
||||
rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner, volume, snapshot);
|
||||
rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner, rootDiskOfferingInfo.getKmsKeyId(), volume, snapshot);
|
||||
}
|
||||
} finally {
|
||||
// Remove volumeContext and pop vmContext back
|
||||
|
||||
@ -165,7 +165,7 @@ public class CloudOrchestrator implements OrchestrationService {
|
||||
public VirtualMachineEntity createVirtualMachine(String id, String owner, String templateId, String hostName, String displayName, String hypervisor, int cpu,
|
||||
int speed, long memory, Long diskSize, List<String> computeTags, List<String> rootDiskTags, Map<String, List<NicProfile>> networkNicMap, DeploymentPlan plan,
|
||||
Long rootDiskSize, Map<String, Map<Integer, String>> extraDhcpOptionMap, Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap, Long dataDiskOfferingId, Long rootDiskOfferingId,
|
||||
List<VmDiskInfo> dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException {
|
||||
Long rootDiskKmsKeyId, List<VmDiskInfo> dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException {
|
||||
|
||||
// VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks,
|
||||
// vmEntityManager);
|
||||
@ -199,6 +199,7 @@ public class CloudOrchestrator implements OrchestrationService {
|
||||
}
|
||||
rootDiskOfferingInfo.setDiskOffering(rootDiskOffering);
|
||||
rootDiskOfferingInfo.setSize(rootDiskSize);
|
||||
rootDiskOfferingInfo.setKmsKeyId(rootDiskKmsKeyId);
|
||||
|
||||
if (rootDiskOffering.isCustomizedIops() != null && rootDiskOffering.isCustomizedIops()) {
|
||||
Map<String, String> userVmDetails = _vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId());
|
||||
@ -281,7 +282,7 @@ public class CloudOrchestrator implements OrchestrationService {
|
||||
@Override
|
||||
public VirtualMachineEntity createVirtualMachineFromScratch(String id, String owner, String isoId, String hostName, String displayName, String hypervisor, String os,
|
||||
int cpu, int speed, long memory, Long diskSize, List<String> computeTags, List<String> rootDiskTags, Map<String, List<NicProfile>> networkNicMap, DeploymentPlan plan,
|
||||
Map<String, Map<Integer, String>> extraDhcpOptionMap, Long diskOfferingId, List<VmDiskInfo> dataDiskInfoList, Volume volume, Snapshot snapshot)
|
||||
Map<String, Map<Integer, String>> extraDhcpOptionMap, Long diskOfferingId, Long rootDiskKmsKeyId, List<VmDiskInfo> dataDiskInfoList, Volume volume, Snapshot snapshot)
|
||||
throws InsufficientCapacityException {
|
||||
|
||||
// VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, vmEntityManager);
|
||||
@ -317,6 +318,7 @@ public class CloudOrchestrator implements OrchestrationService {
|
||||
|
||||
rootDiskOfferingInfo.setDiskOffering(diskOffering);
|
||||
rootDiskOfferingInfo.setSize(size);
|
||||
rootDiskOfferingInfo.setKmsKeyId(rootDiskKmsKeyId);
|
||||
|
||||
if (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops()) {
|
||||
Map<String, String> userVmDetails = _vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId());
|
||||
|
||||
@ -89,6 +89,13 @@ import org.apache.cloudstack.resourcelimit.Reserver;
|
||||
import org.apache.cloudstack.secret.PassphraseVO;
|
||||
import org.apache.cloudstack.secret.dao.PassphraseDao;
|
||||
import org.apache.cloudstack.snapshot.SnapshotHelper;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
import org.apache.cloudstack.kms.KMSKeyVO;
|
||||
import org.apache.cloudstack.kms.KMSWrappedKeyVO;
|
||||
import org.apache.cloudstack.kms.dao.KMSKeyDao;
|
||||
import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.framework.kms.WrappedKey;
|
||||
import org.apache.cloudstack.storage.command.CommandResult;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
@ -287,6 +294,12 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
|
||||
@Inject
|
||||
private DataStoreProviderManager dataStoreProviderMgr;
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
@Inject
|
||||
private KMSKeyDao kmsKeyDao;
|
||||
@Inject
|
||||
private KMSWrappedKeyDao kmsWrappedKeyDao;
|
||||
|
||||
private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine;
|
||||
protected List<StoragePoolAllocator> _storagePoolAllocators;
|
||||
@ -323,7 +336,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
// Find a destination storage pool with the specified criteria
|
||||
DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volumeInfo.getDiskOfferingId());
|
||||
DiskProfile dskCh = new DiskProfile(volumeInfo.getId(), volumeInfo.getVolumeType(), volumeInfo.getName(), diskOffering.getId(), diskOffering.getDiskSize(), diskOffering.getTagsArray(),
|
||||
diskOffering.isUseLocalStorage(), diskOffering.isRecreatable(), null, (diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null));
|
||||
diskOffering.isUseLocalStorage(), diskOffering.isRecreatable(), null, (diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null || volumeInfo.getKmsKeyId() != null));
|
||||
|
||||
dskCh.setHyperType(dataDiskHyperType);
|
||||
storageMgr.setDiskProfileThrottling(dskCh, null, diskOffering);
|
||||
@ -358,9 +371,13 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
newVol.setInstanceId(oldVol.getInstanceId());
|
||||
newVol.setRecreatable(oldVol.isRecreatable());
|
||||
newVol.setFormat(oldVol.getFormat());
|
||||
if ((diskOffering == null || diskOffering.getEncrypt()) && oldVol.getPassphraseId() != null) {
|
||||
PassphraseVO passphrase = passphraseDao.persist(new PassphraseVO(true));
|
||||
newVol.setPassphraseId(passphrase.getId());
|
||||
if ((diskOffering == null || diskOffering.getEncrypt())) {
|
||||
if (oldVol.getKmsKeyId() != null) {
|
||||
newVol.setKmsKeyId(oldVol.getKmsKeyId());
|
||||
} else if (oldVol.getPassphraseId() != null) {
|
||||
PassphraseVO passphrase = passphraseDao.persist(new PassphraseVO(true));
|
||||
newVol.setPassphraseId(passphrase.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return _volsDao.persist(newVol);
|
||||
@ -515,7 +532,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volume.getDiskOfferingId());
|
||||
if (diskOffering.getEncrypt()) {
|
||||
VolumeVO vol = (VolumeVO) volume;
|
||||
volume = setPassphraseForVolumeEncryption(vol);
|
||||
// Retrieve KMS key from volume's kmsKeyId if provided
|
||||
KMSKeyVO kmsKey = getKmsKeyFromVolume(vol);
|
||||
volume = setPassphraseForVolumeEncryption(vol, kmsKey, volume.getAccountId());
|
||||
}
|
||||
DataCenter dc = _entityMgr.findById(DataCenter.class, volume.getDataCenterId());
|
||||
DiskProfile dskCh = new DiskProfile(volume, diskOffering, snapshot.getHypervisorType());
|
||||
@ -652,7 +671,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
}
|
||||
|
||||
protected DiskProfile createDiskCharacteristics(VolumeInfo volumeInfo, VirtualMachineTemplate template, DataCenter dc, DiskOffering diskOffering) {
|
||||
boolean requiresEncryption = diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null;
|
||||
boolean requiresEncryption = diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null || volumeInfo.getKmsKeyId() != null;
|
||||
if (volumeInfo.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO != template.getFormat()) {
|
||||
String templateToString = getReflectOnlySelectedFields(template);
|
||||
String zoneToString = getReflectOnlySelectedFields(dc);
|
||||
@ -732,7 +751,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
|
||||
if (diskOffering.getEncrypt()) {
|
||||
VolumeVO vol = _volsDao.findById(volumeInfo.getId());
|
||||
setPassphraseForVolumeEncryption(vol);
|
||||
// Retrieve KMS key from volume's kmsKeyId if provided
|
||||
KMSKeyVO kmsKey = getKmsKeyFromVolume(vol);
|
||||
setPassphraseForVolumeEncryption(vol, kmsKey, vol.getAccountId());
|
||||
volumeInfo = volFactory.getVolume(volumeInfo.getId());
|
||||
}
|
||||
}
|
||||
@ -996,8 +1017,10 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", create = true)
|
||||
@Override
|
||||
public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner,
|
||||
Long deviceId, boolean incrementResourceCount) {
|
||||
public DiskProfile allocateRawVolume(
|
||||
Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm,
|
||||
VirtualMachineTemplate template, Account owner, Long deviceId, Long kmsKeyId, boolean incrementResourceCount
|
||||
) {
|
||||
if (size == null) {
|
||||
size = offering.getDiskSize();
|
||||
} else {
|
||||
@ -1030,6 +1053,11 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
vol.setDisplayVolume(userVm.isDisplayVm());
|
||||
}
|
||||
|
||||
// Set KMS key ID if provided
|
||||
if (kmsKeyId != null) {
|
||||
vol.setKmsKeyId(kmsKeyId);
|
||||
}
|
||||
|
||||
vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType()));
|
||||
vol = _volsDao.persist(vol);
|
||||
|
||||
@ -1049,7 +1077,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
}
|
||||
|
||||
private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,
|
||||
Account owner, long deviceId, String configurationId, Volume volume, Snapshot snapshot) {
|
||||
Account owner, long deviceId, String configurationId, Long kmsKeyId, Volume volume, Snapshot snapshot) {
|
||||
assert (template.getFormat() != ImageFormat.ISO) : "ISO is not a template.";
|
||||
|
||||
if (volume != null) {
|
||||
@ -1099,6 +1127,11 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
vol.setDisplayVolume(userVm.isDisplayVm());
|
||||
}
|
||||
|
||||
// Set KMS key ID if provided
|
||||
if (kmsKeyId != null) {
|
||||
vol.setKmsKeyId(kmsKeyId);
|
||||
}
|
||||
|
||||
vol = _volsDao.persist(vol);
|
||||
|
||||
saveVolumeDetails(offering.getId(), vol.getId());
|
||||
@ -1188,7 +1221,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT volume", create = true)
|
||||
@Override
|
||||
public List<DiskProfile> allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm,
|
||||
Account owner, Volume volume, Snapshot snapshot) {
|
||||
Account owner, Long kmsKeyId, Volume volume, Snapshot snapshot) {
|
||||
String templateToString = getReflectOnlySelectedFields(template);
|
||||
|
||||
int volumesNumber = 1;
|
||||
@ -1235,7 +1268,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
}
|
||||
logger.info("Adding disk object [{}] to VM [{}]", volumeName, vm);
|
||||
DiskProfile diskProfile = allocateTemplatedVolume(type, volumeName, offering, volumeSize, minIops, maxIops,
|
||||
template, vm, owner, deviceId, configurationId, volume, snapshot);
|
||||
template, vm, owner, deviceId, configurationId, kmsKeyId, volume, snapshot);
|
||||
profiles.add(diskProfile);
|
||||
}
|
||||
|
||||
@ -1945,7 +1978,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
if (vol.getState() == Volume.State.Allocated || vol.getState() == Volume.State.Creating) {
|
||||
DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, vol.getDiskOfferingId());
|
||||
if (diskOffering.getEncrypt()) {
|
||||
vol = setPassphraseForVolumeEncryption(vol);
|
||||
// Retrieve KMS key from volume's kmsKeyId if provided
|
||||
KMSKeyVO kmsKey = getKmsKeyFromVolume(vol);
|
||||
vol = setPassphraseForVolumeEncryption(vol, kmsKey, vol.getAccountId());
|
||||
}
|
||||
newVol = vol;
|
||||
} else {
|
||||
@ -2081,16 +2116,72 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
return new Pair<>(newVol, destPool);
|
||||
}
|
||||
|
||||
private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume) {
|
||||
if (volume.getPassphraseId() != null) {
|
||||
/**
|
||||
* Helper method to retrieve KMS key from volume's kmsKeyId
|
||||
*/
|
||||
private KMSKeyVO getKmsKeyFromVolume(VolumeVO volume) {
|
||||
if (volume.getKmsKeyId() == null) {
|
||||
return null;
|
||||
}
|
||||
return kmsKeyDao.findById(volume.getKmsKeyId());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
logger.debug("Creating passphrase for the volume: " + volume.getName());
|
||||
if (kmsKey != null) {
|
||||
return setKmsKeyForVolumeEncryption(volume, kmsKey, callerAccountId);
|
||||
}
|
||||
// Legacy: passphrase-based encryption (fallback when KMS not enabled or KMS key not specified)
|
||||
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);
|
||||
}
|
||||
|
||||
@ -2119,7 +2210,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
PrimaryDataStoreDriver driver = (PrimaryDataStoreDriver) store.getDriver();
|
||||
long newSize = driver.getVolumeSizeRequiredOnPool(vol.getSize(),
|
||||
template == null ? null : template.getSize(),
|
||||
vol.getPassphraseId() != null);
|
||||
vol.getPassphraseId() != null || vol.getKmsKeyId() != null);
|
||||
|
||||
if (newSize == vol.getSize()) {
|
||||
return;
|
||||
|
||||
@ -280,6 +280,7 @@ public class VolumeOrchestratorTest {
|
||||
Mockito.when(oldVol.isRecreatable()).thenReturn(false);
|
||||
Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
|
||||
Mockito.when(oldVol.getPassphraseId()).thenReturn(null); // no encryption
|
||||
Mockito.when(oldVol.getKmsKeyId()).thenReturn(null); // no encryption
|
||||
|
||||
VolumeVO persistedVol = Mockito.mock(VolumeVO.class);
|
||||
Mockito.when(volumeDao.persist(Mockito.any(VolumeVO.class))).thenReturn(persistedVol);
|
||||
@ -308,6 +309,7 @@ public class VolumeOrchestratorTest {
|
||||
Mockito.when(oldVol.getInstanceId()).thenReturn(7L);
|
||||
Mockito.when(oldVol.isRecreatable()).thenReturn(true);
|
||||
Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.RAW);
|
||||
Mockito.when(oldVol.getKmsKeyId()).thenReturn(null);
|
||||
Mockito.when(oldVol.getPassphraseId()).thenReturn(42L);
|
||||
|
||||
PassphraseVO passphrase = Mockito.mock(PassphraseVO.class);
|
||||
@ -341,9 +343,6 @@ public class VolumeOrchestratorTest {
|
||||
VolumeVO persistedVol = Mockito.mock(VolumeVO.class);
|
||||
Mockito.when(volumeDao.persist(Mockito.any())).thenReturn(persistedVol);
|
||||
|
||||
PassphraseVO mockPassPhrase = Mockito.mock(PassphraseVO.class);
|
||||
Mockito.when(passphraseDao.persist(Mockito.any())).thenReturn(mockPassPhrase);
|
||||
|
||||
VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, null, 222L);
|
||||
assertNotNull(result);
|
||||
}
|
||||
|
||||
@ -48,6 +48,11 @@
|
||||
<artifactId>cloud-framework-db</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-kms</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
|
||||
@ -182,6 +182,12 @@ public class VolumeVO implements Volume {
|
||||
@Column(name = "passphrase_id")
|
||||
private Long passphraseId;
|
||||
|
||||
@Column(name = "kms_key_id")
|
||||
private Long kmsKeyId;
|
||||
|
||||
@Column(name = "kms_wrapped_key_id")
|
||||
private Long kmsWrappedKeyId;
|
||||
|
||||
@Column(name = "encrypt_format")
|
||||
private String encryptFormat;
|
||||
|
||||
@ -683,6 +689,14 @@ public class VolumeVO implements Volume {
|
||||
|
||||
public void setPassphraseId(Long id) { this.passphraseId = id; }
|
||||
|
||||
public Long getKmsKeyId() { return kmsKeyId; }
|
||||
|
||||
public void setKmsKeyId(Long id) { this.kmsKeyId = id; }
|
||||
|
||||
public Long getKmsWrappedKeyId() { return kmsWrappedKeyId; }
|
||||
|
||||
public void setKmsWrappedKeyId(Long id) { this.kmsWrappedKeyId = id; }
|
||||
|
||||
public String getEncryptFormat() { return encryptFormat; }
|
||||
|
||||
public void setEncryptFormat(String encryptFormat) { this.encryptFormat = encryptFormat; }
|
||||
|
||||
@ -111,6 +111,17 @@ public interface VolumeDao extends GenericDao<VolumeVO, Long>, StateDao<Volume.S
|
||||
*/
|
||||
List<VolumeVO> listVolumesByPassphraseId(long passphraseId);
|
||||
|
||||
/**
|
||||
* List volumes with passphrase_id for migration to KMS
|
||||
*
|
||||
* @param zoneId Zone ID (required)
|
||||
* @param accountId Account ID filter (optional, null for all accounts)
|
||||
* @param domainId Domain ID filter (optional, null for all domains)
|
||||
* @param limit Maximum number of volumes to return
|
||||
* @return list of volumes that need migration
|
||||
*/
|
||||
Pair<List<VolumeVO>, Integer> listVolumesForKMSMigration(Long zoneId, Long accountId, Long domainId, Integer limit);
|
||||
|
||||
/**
|
||||
* Gets the Total Primary Storage space allocated for an account
|
||||
*
|
||||
@ -169,6 +180,8 @@ public interface VolumeDao extends GenericDao<VolumeVO, Long>, StateDao<Volume.S
|
||||
|
||||
VolumeVO findByLastIdAndState(long lastVolumeId, Volume.State...states);
|
||||
|
||||
boolean existsWithKmsKey(long kmsKeyId);
|
||||
|
||||
/**
|
||||
* Retrieves volume by its externalId
|
||||
*
|
||||
@ -176,4 +189,6 @@ public interface VolumeDao extends GenericDao<VolumeVO, Long>, StateDao<Volume.S
|
||||
* @return Volume Object of matching search criteria
|
||||
*/
|
||||
VolumeVO findByExternalUuid(String externalUuid);
|
||||
|
||||
List<VolumeVO> findByKmsWrappedKeyId(Long kmsWrappedKeyId);
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.reservation.ReservationVO;
|
||||
import org.apache.cloudstack.reservation.dao.ReservationDao;
|
||||
import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -80,11 +81,14 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
|
||||
protected GenericSearchBuilder<VolumeVO, SumCount> secondaryStorageSearch;
|
||||
private final SearchBuilder<VolumeVO> poolAndPathSearch;
|
||||
final GenericSearchBuilder<VolumeVO, Integer> CountByOfferingId;
|
||||
private final SearchBuilder<VolumeVO> kmsMigrationSearch;
|
||||
|
||||
@Inject
|
||||
ReservationDao reservationDao;
|
||||
@Inject
|
||||
ResourceTagDao tagsDao;
|
||||
@Inject
|
||||
KMSWrappedKeyDao kmsWrappedKeyDao;
|
||||
|
||||
// need to account for zone-wide primary storage where storage_pool has
|
||||
// null-value pod and cluster, where hypervisor information is stored in
|
||||
@ -412,6 +416,8 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
|
||||
AllFieldsSearch.and("passphraseId", AllFieldsSearch.entity().getPassphraseId(), Op.EQ);
|
||||
AllFieldsSearch.and("iScsiName", AllFieldsSearch.entity().get_iScsiName(), Op.EQ);
|
||||
AllFieldsSearch.and("path", AllFieldsSearch.entity().getPath(), Op.EQ);
|
||||
AllFieldsSearch.and("kmsKeyId", AllFieldsSearch.entity().getKmsKeyId(), Op.EQ);
|
||||
AllFieldsSearch.and("kmsWrappedKeyId", AllFieldsSearch.entity().getKmsWrappedKeyId(), Op.EQ);
|
||||
AllFieldsSearch.done();
|
||||
|
||||
RootDiskStateSearch = createSearchBuilder();
|
||||
@ -528,6 +534,13 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
|
||||
CountByOfferingId.select(null, Func.COUNT, CountByOfferingId.entity().getId());
|
||||
CountByOfferingId.and("diskOfferingId", CountByOfferingId.entity().getDiskOfferingId(), Op.EQ);
|
||||
CountByOfferingId.done();
|
||||
|
||||
kmsMigrationSearch = createSearchBuilder();
|
||||
kmsMigrationSearch.and("passphraseId", kmsMigrationSearch.entity().getPassphraseId(), Op.NNULL);
|
||||
kmsMigrationSearch.and("zoneId", kmsMigrationSearch.entity().getDataCenterId(), Op.EQ);
|
||||
kmsMigrationSearch.and("accountId", kmsMigrationSearch.entity().getAccountId(), Op.EQ);
|
||||
kmsMigrationSearch.and("domainId", kmsMigrationSearch.entity().getDomainId(), Op.EQ);
|
||||
kmsMigrationSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -748,6 +761,21 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<List<VolumeVO>, Integer> listVolumesForKMSMigration(Long zoneId, Long accountId, Long domainId, Integer limit) {
|
||||
SearchCriteria<VolumeVO> sc = kmsMigrationSearch.create();
|
||||
|
||||
Filter filter = new Filter(limit);
|
||||
sc.setParameters("zoneId", zoneId);
|
||||
if (accountId != null) {
|
||||
sc.setParameters("accountId", accountId);
|
||||
}
|
||||
if (domainId != null) {
|
||||
sc.setParameters("domainId", domainId);
|
||||
}
|
||||
return searchAndCount(sc, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@DB
|
||||
public boolean remove(Long id) {
|
||||
@ -756,6 +784,17 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
|
||||
logger.debug(String.format("Removing volume %s from DB", id));
|
||||
VolumeVO entry = findById(id);
|
||||
if (entry != null) {
|
||||
// Clean up KMS wrapped key if volume was encrypted with KMS
|
||||
if (entry.getKmsWrappedKeyId() != null) {
|
||||
try {
|
||||
kmsWrappedKeyDao.remove(entry.getKmsWrappedKeyId());
|
||||
logger.debug("Removed KMS wrapped key [id={}] for volume [id={}, uuid={}]",
|
||||
entry.getKmsWrappedKeyId(), id, entry.getUuid());
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to remove KMS wrapped key [id={}] for volume [id={}, uuid={}]: {}",
|
||||
entry.getKmsWrappedKeyId(), id, entry.getUuid(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
tagsDao.removeByIdAndType(id, ResourceObjectType.Volume);
|
||||
}
|
||||
boolean result = super.remove(id);
|
||||
@ -952,9 +991,24 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsWithKmsKey(long kmsKeyId) {
|
||||
SearchCriteria<VolumeVO> sc = AllFieldsSearch.create();
|
||||
sc.setParameters("kmsKeyId", kmsKeyId);
|
||||
sc.setParameters("notDestroyed", Volume.State.Expunged, Volume.State.Destroy);
|
||||
return findOneBy(sc) != null;
|
||||
}
|
||||
|
||||
public VolumeVO findByExternalUuid(String externalUuid) {
|
||||
SearchCriteria<VolumeVO> sc = ExternalUuidSearch.create();
|
||||
sc.setParameters("externalUuid", externalUuid);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VolumeVO> findByKmsWrappedKeyId(Long kmsWrappedKeyId) {
|
||||
SearchCriteria<VolumeVO> sc = AllFieldsSearch.create();
|
||||
sc.setParameters("kmsWrappedKeyId", kmsWrappedKeyId);
|
||||
sc.setParameters("notDestroyed", Volume.State.Expunged);
|
||||
return listBy(sc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms;
|
||||
|
||||
import org.apache.cloudstack.api.ResourceDetail;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "kms_hsm_profile_details")
|
||||
public class HSMProfileDetailsVO implements ResourceDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private long id;
|
||||
|
||||
@Column(name = "profile_id")
|
||||
private long resourceId;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
@Column(name = "value")
|
||||
private String value;
|
||||
|
||||
public HSMProfileDetailsVO() {
|
||||
}
|
||||
|
||||
public HSMProfileDetailsVO(long profileId, String name, String value) {
|
||||
this.resourceId = profileId;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplay() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms;
|
||||
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "kms_hsm_profiles")
|
||||
public class HSMProfileVO implements HSMProfile {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private long id;
|
||||
|
||||
@Column(name = "uuid")
|
||||
private String uuid;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
@Column(name = "protocol")
|
||||
private String protocol;
|
||||
|
||||
@Column(name = "account_id")
|
||||
private Long accountId;
|
||||
|
||||
@Column(name = "domain_id")
|
||||
private Long domainId;
|
||||
|
||||
@Column(name = "zone_id")
|
||||
private Long zoneId;
|
||||
|
||||
@Column(name = "vendor_name")
|
||||
private String vendorName;
|
||||
|
||||
@Column(name = "enabled")
|
||||
private boolean enabled;
|
||||
|
||||
@Column(name = "is_public")
|
||||
private boolean isPublic;
|
||||
|
||||
@Column(name = "created")
|
||||
private Date created;
|
||||
|
||||
@Column(name = "removed")
|
||||
private Date removed;
|
||||
|
||||
public HSMProfileVO() {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.created = new Date();
|
||||
this.isPublic = false;
|
||||
}
|
||||
|
||||
public HSMProfileVO(String name, String protocol, Long accountId, Long domainId, Long zoneId, String vendorName) {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.name = name;
|
||||
this.protocol = protocol;
|
||||
this.accountId = accountId;
|
||||
this.domainId = domainId;
|
||||
this.zoneId = zoneId;
|
||||
this.vendorName = vendorName;
|
||||
this.enabled = true;
|
||||
this.isPublic = false;
|
||||
this.created = new Date();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("HSMProfileVO %s",
|
||||
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "name", "protocol", "system", "enabled"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAccountId() {
|
||||
return accountId == null ? -1 : accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDomainId() {
|
||||
return domainId == null ? -1 : domainId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVendorName() {
|
||||
return vendorName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getRemoved() {
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEntityType() {
|
||||
return HSMProfile.class;
|
||||
}
|
||||
|
||||
public void setRemoved(Date removed) {
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setVendorName(String vendorName) {
|
||||
this.vendorName = vendorName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getIsPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
|
||||
public void setIsPublic(boolean isPublic) {
|
||||
this.isPublic = isPublic;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Database entity for KEK versions.
|
||||
* Tracks multiple KEK versions per KMS key to support gradual rotation.
|
||||
* During rotation, a new version is created (status=Active) and old versions
|
||||
* are marked as Previous (still usable for decryption) or Archived (no longer used).
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "kms_kek_versions")
|
||||
public class KMSKekVersionVO {
|
||||
|
||||
public enum Status {
|
||||
/**
|
||||
* Used for new encryption operations
|
||||
*/
|
||||
Active,
|
||||
/**
|
||||
* Still usable for decryption during key rotation
|
||||
*/
|
||||
Previous,
|
||||
/**
|
||||
* No longer used; all wrapped keys have been re-encrypted
|
||||
*/
|
||||
Archived
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "uuid", nullable = false)
|
||||
private String uuid;
|
||||
|
||||
@Column(name = "kms_key_id", nullable = false)
|
||||
private Long kmsKeyId;
|
||||
|
||||
@Column(name = "version_number", nullable = false)
|
||||
private Integer versionNumber;
|
||||
|
||||
@Column(name = "kek_label", nullable = false)
|
||||
private String kekLabel;
|
||||
|
||||
@Column(name = "status", nullable = false, length = 32)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Status status;
|
||||
|
||||
@Column(name = "hsm_profile_id")
|
||||
private Long hsmProfileId;
|
||||
|
||||
@Column(name = GenericDao.CREATED_COLUMN, nullable = false)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
||||
@Column(name = GenericDao.REMOVED_COLUMN)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date removed;
|
||||
|
||||
public KMSKekVersionVO(Long kmsKeyId, Integer versionNumber, String kekLabel) {
|
||||
this();
|
||||
this.kmsKeyId = kmsKeyId;
|
||||
this.versionNumber = versionNumber;
|
||||
this.kekLabel = kekLabel;
|
||||
this.status = Status.Active;
|
||||
}
|
||||
|
||||
public KMSKekVersionVO(Long kmsKeyId, String kekLabel) {
|
||||
this();
|
||||
this.kmsKeyId = kmsKeyId;
|
||||
this.kekLabel = kekLabel;
|
||||
this.status = Status.Active;
|
||||
}
|
||||
|
||||
public KMSKekVersionVO() {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.created = new Date();
|
||||
this.status = Status.Active;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public Long getKmsKeyId() {
|
||||
return kmsKeyId;
|
||||
}
|
||||
|
||||
public void setKmsKeyId(Long kmsKeyId) {
|
||||
this.kmsKeyId = kmsKeyId;
|
||||
}
|
||||
|
||||
public Integer getVersionNumber() {
|
||||
return versionNumber;
|
||||
}
|
||||
|
||||
public void setVersionNumber(Integer versionNumber) {
|
||||
this.versionNumber = versionNumber;
|
||||
}
|
||||
|
||||
public String getKekLabel() {
|
||||
return kekLabel;
|
||||
}
|
||||
|
||||
public void setKekLabel(String kekLabel) {
|
||||
this.kekLabel = kekLabel;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Long getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
||||
public void setHsmProfileId(Long hsmProfileId) {
|
||||
this.hsmProfileId = hsmProfileId;
|
||||
}
|
||||
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public Date getRemoved() {
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void setRemoved(Date removed) {
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("KMSKekVersion %s",
|
||||
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "kmsKeyId", "versionNumber", "status", "kekLabel"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,264 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Database entity for KMS Key (Key Encryption Key) metadata.
|
||||
* Tracks ownership, purpose, and lifecycle of KEKs used in envelope encryption.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "kms_keys")
|
||||
public class KMSKeyVO implements KMSKey {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "uuid", nullable = false)
|
||||
private String uuid;
|
||||
|
||||
@Column(name = "name", nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "description", length = 1024)
|
||||
private String description;
|
||||
|
||||
@Column(name = "kek_label", nullable = false)
|
||||
private String kekLabel;
|
||||
|
||||
@Column(name = "purpose", nullable = false, length = 32)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private KeyPurpose purpose;
|
||||
|
||||
@Column(name = "account_id", nullable = false)
|
||||
private Long accountId;
|
||||
|
||||
@Column(name = "domain_id", nullable = false)
|
||||
private Long domainId;
|
||||
|
||||
@Column(name = "zone_id", nullable = false)
|
||||
private Long zoneId;
|
||||
|
||||
@Column(name = "algorithm", nullable = false, length = 64)
|
||||
private String algorithm;
|
||||
|
||||
@Column(name = "key_bits", nullable = false)
|
||||
private Integer keyBits;
|
||||
|
||||
@Column(name = "enabled", nullable = false)
|
||||
private boolean enabled;
|
||||
|
||||
@Column(name = "hsm_profile_id")
|
||||
private Long hsmProfileId;
|
||||
|
||||
@Column(name = GenericDao.CREATED_COLUMN, nullable = false)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
||||
@Column(name = GenericDao.REMOVED_COLUMN)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date removed;
|
||||
|
||||
public KMSKeyVO(String name, String description, String kekLabel,
|
||||
KeyPurpose purpose, Long accountId, Long domainId,
|
||||
Long zoneId, String algorithm, Integer keyBits
|
||||
) {
|
||||
this();
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.kekLabel = kekLabel;
|
||||
this.purpose = purpose;
|
||||
this.accountId = accountId;
|
||||
this.domainId = domainId;
|
||||
this.zoneId = zoneId;
|
||||
this.algorithm = algorithm;
|
||||
this.keyBits = keyBits;
|
||||
}
|
||||
|
||||
public KMSKeyVO() {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.created = new Date();
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKekLabel() {
|
||||
return kekLabel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPurpose getPurpose() {
|
||||
return purpose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getKeyBits() {
|
||||
return keyBits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getRemoved() {
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getHsmProfileId() {
|
||||
return hsmProfileId;
|
||||
}
|
||||
|
||||
public void setHsmProfileId(Long hsmProfileId) {
|
||||
this.hsmProfileId = hsmProfileId;
|
||||
}
|
||||
|
||||
public void setRemoved(Date removed) {
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setKeyBits(Integer keyBits) {
|
||||
this.keyBits = keyBits;
|
||||
}
|
||||
|
||||
public void setAlgorithm(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public void setZoneId(Long zoneId) {
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public void setPurpose(KeyPurpose purpose) {
|
||||
this.purpose = purpose;
|
||||
}
|
||||
|
||||
public void setKekLabel(String kekLabel) {
|
||||
this.kekLabel = kekLabel;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public void setAccountId(Long accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public void setDomainId(Long domainId) {
|
||||
this.domainId = domainId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEntityType() {
|
||||
return KMSKey.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("KMSKey %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "name", "purpose",
|
||||
"accountId", "zoneId", "enabled"
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,176 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Database entity for storing wrapped (encrypted) Data Encryption Keys.
|
||||
* Each entry represents a DEK that has been encrypted by a Key Encryption Key (KEK).
|
||||
* KEK metadata is stored in kms_keys table via the kms_key_id foreign key.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "kms_wrapped_key")
|
||||
public class KMSWrappedKeyVO {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "uuid", nullable = false)
|
||||
private String uuid;
|
||||
|
||||
@Column(name = "kms_key_id")
|
||||
private Long kmsKeyId;
|
||||
|
||||
@Column(name = "kek_version_id")
|
||||
private Long kekVersionId;
|
||||
|
||||
@Column(name = "zone_id", nullable = false)
|
||||
private Long zoneId;
|
||||
|
||||
@Column(name = "wrapped_blob", nullable = false)
|
||||
private byte[] wrappedBlob;
|
||||
|
||||
@Column(name = GenericDao.CREATED_COLUMN, nullable = false)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
||||
@Column(name = GenericDao.REMOVED_COLUMN)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date removed;
|
||||
|
||||
public KMSWrappedKeyVO(KMSKeyVO kmsKey, byte[] wrappedBlob) {
|
||||
this();
|
||||
this.kmsKeyId = kmsKey.getId();
|
||||
this.zoneId = kmsKey.getZoneId();
|
||||
this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
|
||||
}
|
||||
|
||||
public KMSWrappedKeyVO() {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.created = new Date();
|
||||
}
|
||||
|
||||
public KMSWrappedKeyVO(KMSKeyVO kmsKey, Long kekVersionId, byte[] wrappedBlob) {
|
||||
this();
|
||||
this.kmsKeyId = kmsKey.getId();
|
||||
this.kekVersionId = kekVersionId;
|
||||
this.zoneId = kmsKey.getZoneId();
|
||||
this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
|
||||
}
|
||||
|
||||
public KMSWrappedKeyVO(Long kmsKeyId, Long zoneId, byte[] wrappedBlob) {
|
||||
this();
|
||||
this.kmsKeyId = kmsKeyId;
|
||||
this.zoneId = zoneId;
|
||||
this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
|
||||
}
|
||||
|
||||
public KMSWrappedKeyVO(Long kmsKeyId, Long kekVersionId, Long zoneId, byte[] wrappedBlob) {
|
||||
this();
|
||||
this.kmsKeyId = kmsKeyId;
|
||||
this.kekVersionId = kekVersionId;
|
||||
this.zoneId = zoneId;
|
||||
this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public Long getKmsKeyId() {
|
||||
return kmsKeyId;
|
||||
}
|
||||
|
||||
public void setKmsKeyId(Long kmsKeyId) {
|
||||
this.kmsKeyId = kmsKeyId;
|
||||
}
|
||||
|
||||
public Long getKekVersionId() {
|
||||
return kekVersionId;
|
||||
}
|
||||
|
||||
public void setKekVersionId(Long kekVersionId) {
|
||||
this.kekVersionId = kekVersionId;
|
||||
}
|
||||
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public void setZoneId(Long zoneId) {
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public byte[] getWrappedBlob() {
|
||||
return wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
|
||||
}
|
||||
|
||||
public void setWrappedBlob(byte[] wrappedBlob) {
|
||||
this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public Date getRemoved() {
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void setRemoved(Date removed) {
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("KMSWrappedKey %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "kmsKeyId", "kekVersionId", "zoneId", "created", "removed"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.kms.HSMProfileVO;
|
||||
|
||||
public interface HSMProfileDao extends GenericDao<HSMProfileVO, Long> {
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import org.apache.cloudstack.kms.HSMProfileVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class HSMProfileDaoImpl extends GenericDaoBase<HSMProfileVO, Long> implements HSMProfileDao {
|
||||
public HSMProfileDaoImpl() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.kms.HSMProfileDetailsVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface HSMProfileDetailsDao extends GenericDao<HSMProfileDetailsVO, Long> {
|
||||
List<HSMProfileDetailsVO> listByProfileId(long profileId);
|
||||
|
||||
void persist(long profileId, String name, String value);
|
||||
|
||||
HSMProfileDetailsVO findDetail(long profileId, String name);
|
||||
|
||||
void deleteDetails(long profileId);
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.SearchCriteria.Op;
|
||||
import org.apache.cloudstack.kms.HSMProfileDetailsVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class HSMProfileDetailsDaoImpl extends GenericDaoBase<HSMProfileDetailsVO, Long> implements HSMProfileDetailsDao {
|
||||
|
||||
protected SearchBuilder<HSMProfileDetailsVO> ProfileSearch;
|
||||
protected SearchBuilder<HSMProfileDetailsVO> DetailSearch;
|
||||
|
||||
public HSMProfileDetailsDaoImpl() {
|
||||
super();
|
||||
|
||||
ProfileSearch = createSearchBuilder();
|
||||
ProfileSearch.and("profileId", ProfileSearch.entity().getResourceId(), Op.EQ);
|
||||
ProfileSearch.done();
|
||||
|
||||
DetailSearch = createSearchBuilder();
|
||||
DetailSearch.and("profileId", DetailSearch.entity().getResourceId(), Op.EQ);
|
||||
DetailSearch.and("name", DetailSearch.entity().getName(), Op.EQ);
|
||||
DetailSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HSMProfileDetailsVO> listByProfileId(long profileId) {
|
||||
SearchCriteria<HSMProfileDetailsVO> sc = ProfileSearch.create();
|
||||
sc.setParameters("profileId", profileId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void persist(long profileId, String name, String value) {
|
||||
HSMProfileDetailsVO vo = new HSMProfileDetailsVO(profileId, name, value);
|
||||
persist(vo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HSMProfileDetailsVO findDetail(long profileId, String name) {
|
||||
SearchCriteria<HSMProfileDetailsVO> sc = DetailSearch.create();
|
||||
sc.setParameters("profileId", profileId);
|
||||
sc.setParameters("name", name);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDetails(long profileId) {
|
||||
SearchCriteria<HSMProfileDetailsVO> sc = ProfileSearch.create();
|
||||
sc.setParameters("profileId", profileId);
|
||||
remove(sc);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.kms.KMSKekVersionVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface KMSKekVersionDao extends GenericDao<KMSKekVersionVO, Long> {
|
||||
|
||||
KMSKekVersionVO getActiveVersion(Long kmsKeyId);
|
||||
|
||||
/**
|
||||
* Returns Active and Previous versions (usable for decryption)
|
||||
*/
|
||||
List<KMSKekVersionVO> getVersionsForDecryption(Long kmsKeyId);
|
||||
|
||||
List<KMSKekVersionVO> listByKmsKeyId(Long kmsKeyId);
|
||||
|
||||
KMSKekVersionVO findByKmsKeyIdAndVersion(Long kmsKeyId, Integer versionNumber);
|
||||
|
||||
KMSKekVersionVO findByKekLabel(String kekLabel);
|
||||
|
||||
List<KMSKekVersionVO> findByStatus(KMSKekVersionVO.Status status);
|
||||
|
||||
List<KMSKekVersionVO> listByHsmProfileId(Long hsmProfileId);
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.kms.KMSKekVersionVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class KMSKekVersionDaoImpl extends GenericDaoBase<KMSKekVersionVO, Long> implements KMSKekVersionDao {
|
||||
|
||||
private final SearchBuilder<KMSKekVersionVO> allFieldSearch;
|
||||
|
||||
public KMSKekVersionDaoImpl() {
|
||||
allFieldSearch = createSearchBuilder();
|
||||
allFieldSearch.and("kmsKeyId", allFieldSearch.entity().getKmsKeyId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("status", allFieldSearch.entity().getStatus(), SearchCriteria.Op.IN);
|
||||
allFieldSearch.and("versionNumber", allFieldSearch.entity().getVersionNumber(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("kekLabel", allFieldSearch.entity().getKekLabel(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("hsmProfileId", allFieldSearch.entity().getHsmProfileId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KMSKekVersionVO getActiveVersion(Long kmsKeyId) {
|
||||
SearchCriteria<KMSKekVersionVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("kmsKeyId", kmsKeyId);
|
||||
sc.setParameters("status", KMSKekVersionVO.Status.Active);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSKekVersionVO> getVersionsForDecryption(Long kmsKeyId) {
|
||||
SearchCriteria<KMSKekVersionVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("kmsKeyId", kmsKeyId);
|
||||
sc.setParameters("status", KMSKekVersionVO.Status.Active, KMSKekVersionVO.Status.Previous);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSKekVersionVO> listByKmsKeyId(Long kmsKeyId) {
|
||||
SearchCriteria<KMSKekVersionVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("kmsKeyId", kmsKeyId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KMSKekVersionVO findByKmsKeyIdAndVersion(Long kmsKeyId, Integer versionNumber) {
|
||||
SearchCriteria<KMSKekVersionVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("kmsKeyId", kmsKeyId);
|
||||
sc.setParameters("versionNumber", versionNumber);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KMSKekVersionVO findByKekLabel(String kekLabel) {
|
||||
SearchCriteria<KMSKekVersionVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("kekLabel", kekLabel);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSKekVersionVO> findByStatus(KMSKekVersionVO.Status status) {
|
||||
SearchCriteria<KMSKekVersionVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("status", status);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSKekVersionVO> listByHsmProfileId(Long hsmProfileId) {
|
||||
if (hsmProfileId == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
SearchCriteria<KMSKekVersionVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("hsmProfileId", hsmProfileId);
|
||||
return listBy(sc);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
import org.apache.cloudstack.kms.KMSKeyVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface KMSKeyDao extends GenericDao<KMSKeyVO, Long> {
|
||||
|
||||
List<KMSKeyVO> listByAccount(Long accountId, KeyPurpose purpose, Boolean enabled);
|
||||
|
||||
List<KMSKeyVO> listByZone(Long zoneId, KeyPurpose purpose, Boolean enabled);
|
||||
|
||||
long countByHsmProfileId(Long hsmProfileId);
|
||||
|
||||
KMSKeyVO findByNameAndAccountId(String name, long accountId);
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
import org.apache.cloudstack.kms.KMSKeyVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class KMSKeyDaoImpl extends GenericDaoBase<KMSKeyVO, Long> implements KMSKeyDao {
|
||||
|
||||
private final SearchBuilder<KMSKeyVO> allFieldSearch;
|
||||
|
||||
public KMSKeyDaoImpl() {
|
||||
allFieldSearch = createSearchBuilder();
|
||||
allFieldSearch.and("name", allFieldSearch.entity().getName(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("kekLabel", allFieldSearch.entity().getKekLabel(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("domainId", allFieldSearch.entity().getDomainId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("accountId", allFieldSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("purpose", allFieldSearch.entity().getPurpose(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("enabled", allFieldSearch.entity().isEnabled(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("zoneId", allFieldSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("hsmProfileId", allFieldSearch.entity().getHsmProfileId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSKeyVO> listByAccount(Long accountId, KeyPurpose purpose, Boolean enabled) {
|
||||
SearchCriteria<KMSKeyVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("accountId", accountId);
|
||||
sc.setParametersIfNotNull("purpose", purpose);
|
||||
sc.setParametersIfNotNull("enabled", enabled);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSKeyVO> listByZone(Long zoneId, KeyPurpose purpose, Boolean enabled) {
|
||||
SearchCriteria<KMSKeyVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("zoneId", zoneId);
|
||||
sc.setParametersIfNotNull("purpose", purpose);
|
||||
sc.setParametersIfNotNull("enabled", enabled);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByHsmProfileId(Long hsmProfileId) {
|
||||
if (hsmProfileId == null) {
|
||||
return 0;
|
||||
}
|
||||
SearchCriteria<KMSKeyVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("hsmProfileId", hsmProfileId);
|
||||
Integer count = getCount(sc);
|
||||
return count != null ? count : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KMSKeyVO findByNameAndAccountId(String name, long accountId) {
|
||||
SearchCriteria<KMSKeyVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("name", name);
|
||||
sc.setParameters("accountId", accountId);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.kms.KMSWrappedKeyVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface KMSWrappedKeyDao extends GenericDao<KMSWrappedKeyVO, Long> {
|
||||
|
||||
long countByKmsKeyId(Long kmsKeyId);
|
||||
|
||||
/**
|
||||
* Limited variant for batch processing during key rotation
|
||||
*/
|
||||
List<KMSWrappedKeyVO> listByKekVersionId(Long kekVersionId, int limit);
|
||||
|
||||
long countByKekVersionId(Long kekVersionId);
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.dao;
|
||||
|
||||
import com.cloud.utils.db.Filter;
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.kms.KMSWrappedKeyVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class KMSWrappedKeyDaoImpl extends GenericDaoBase<KMSWrappedKeyVO, Long> implements KMSWrappedKeyDao {
|
||||
|
||||
private final SearchBuilder<KMSWrappedKeyVO> allFieldSearch;
|
||||
|
||||
public KMSWrappedKeyDaoImpl() {
|
||||
super();
|
||||
|
||||
allFieldSearch = createSearchBuilder();
|
||||
allFieldSearch.and("kmsKeyId", allFieldSearch.entity().getKmsKeyId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("kekVersionId", allFieldSearch.entity().getKekVersionId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("zoneId", allFieldSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByKmsKeyId(Long kmsKeyId) {
|
||||
SearchCriteria<KMSWrappedKeyVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("kmsKeyId", kmsKeyId);
|
||||
Integer count = getCount(sc);
|
||||
return count != null ? count.longValue() : 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSWrappedKeyVO> listByKekVersionId(Long kekVersionId, int limit) {
|
||||
SearchCriteria<KMSWrappedKeyVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("kekVersionId", kekVersionId);
|
||||
Filter filter = new Filter(limit);
|
||||
return listBy(sc, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByKekVersionId(Long kekVersionId) {
|
||||
if (kekVersionId == null) {
|
||||
return 0;
|
||||
}
|
||||
SearchCriteria<KMSWrappedKeyVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("kekVersionId", kekVersionId);
|
||||
Integer count = getCount(sc);
|
||||
return count != null ? count.longValue() : 0L;
|
||||
}
|
||||
}
|
||||
@ -314,4 +314,9 @@
|
||||
<bean id="importVMTaskDaoImpl" class="com.cloud.vm.dao.ImportVMTaskDaoImpl" />
|
||||
<bean id="apiKeyPairDaoImpl" class="org.apache.cloudstack.acl.dao.ApiKeyPairDaoImpl" />
|
||||
<bean id="apiKeyPairPermissionsDaoImpl" class="org.apache.cloudstack.acl.dao.ApiKeyPairPermissionsDaoImpl" />
|
||||
<bean id="kmsKeyDaoImpl" class="org.apache.cloudstack.kms.dao.KMSKeyDaoImpl" />
|
||||
<bean id="kmsKekVersionDaoImpl" class="org.apache.cloudstack.kms.dao.KMSKekVersionDaoImpl" />
|
||||
<bean id="kmsWrappedKeyDaoImpl" class="org.apache.cloudstack.kms.dao.KMSWrappedKeyDaoImpl" />
|
||||
<bean id="hsmProfileDaoImpl" class="org.apache.cloudstack.kms.dao.HSMProfileDaoImpl" />
|
||||
<bean id="hsmProfileDetailsDaoImpl" class="org.apache.cloudstack.kms.dao.HSMProfileDetailsDaoImpl" />
|
||||
</beans>
|
||||
|
||||
@ -119,6 +119,171 @@ CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'listUserKeyRu
|
||||
-- Add conserve mode for VPC offerings
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tinyint(1) unsigned NULL DEFAULT 0 COMMENT ''True if the VPC offering is IP conserve mode enabled, allowing public IP services to be used across multiple VPC tiers'' ');
|
||||
|
||||
-- KMS HSM Profiles (Generic table for PKCS#11, KMIP, etc.)
|
||||
-- Scoped to account (user-provided) or global/zone (admin-provided)
|
||||
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 'Protocol for the HSM Profile',
|
||||
|
||||
-- Scoping
|
||||
`account_id` BIGINT UNSIGNED COMMENT 'null = admin-provided (available to all accounts)',
|
||||
`domain_id` BIGINT UNSIGNED COMMENT 'null = zone/global scope',
|
||||
`zone_id` BIGINT UNSIGNED COMMENT 'null = global scope',
|
||||
|
||||
-- Metadata
|
||||
`vendor_name` VARCHAR(64) COMMENT 'HSM vendor',
|
||||
`enabled` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`is_public` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Public profile. Available to all accounts',
|
||||
`created` DATETIME NOT NULL,
|
||||
`removed` DATETIME,
|
||||
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_uuid` (`uuid`),
|
||||
UNIQUE KEY `uk_account_name` (`account_id`, `name`, `removed`),
|
||||
INDEX `idx_protocol_enabled` (`protocol`, `enabled`, `removed`),
|
||||
INDEX `idx_scoping` (`account_id`, `domain_id`, `zone_id`, `removed`),
|
||||
CONSTRAINT `fk_kms_hsm_profiles__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_hsm_profiles__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_hsm_profiles__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='HSM profiles for KMS providers';
|
||||
|
||||
-- KMS HSM Profile Details (Protocol-specific configuration)
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profile_details` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`profile_id` BIGINT UNSIGNED NOT NULL COMMENT 'HSM profile ID',
|
||||
`name` VARCHAR(255) NOT NULL COMMENT 'Config key (e.g. library_path, endpoint, pin, cert_content)',
|
||||
`value` TEXT NOT NULL COMMENT 'Config value (encrypted if sensitive)',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_profile_name` (`profile_id`, `name`),
|
||||
CONSTRAINT `fk_kms_hsm_profile_details__profile_id` FOREIGN KEY (`profile_id`) REFERENCES `kms_hsm_profiles`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Details for HSM profiles (key-value configuration)';
|
||||
|
||||
-- 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,
|
||||
`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',
|
||||
`kek_label` VARCHAR(255) NOT NULL COMMENT 'Provider-specific KEK label/ID',
|
||||
`purpose` VARCHAR(32) NOT NULL COMMENT 'Key purpose (VOLUME_ENCRYPTION, TLS_CERT)',
|
||||
`account_id` BIGINT UNSIGNED NOT NULL COMMENT 'Owning account',
|
||||
`domain_id` BIGINT UNSIGNED NOT NULL COMMENT 'Owning domain',
|
||||
`zone_id` BIGINT UNSIGNED NOT NULL COMMENT 'Zone where key is valid',
|
||||
`algorithm` VARCHAR(64) NOT NULL DEFAULT 'AES/GCM/NoPadding' COMMENT 'Encryption algorithm',
|
||||
`key_bits` INT NOT NULL DEFAULT 256 COMMENT 'Key size in bits',
|
||||
`enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'Whether the key is enabled for new cryptographic operations',
|
||||
`hsm_profile_id` BIGINT UNSIGNED NOT NULL COMMENT 'Current HSM profile ID for this key',
|
||||
`created` DATETIME NOT NULL COMMENT 'Creation timestamp',
|
||||
`removed` DATETIME COMMENT 'Removal timestamp for soft delete',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_uuid` (`uuid`),
|
||||
INDEX `idx_account_purpose` (`account_id`, `purpose`, `enabled`),
|
||||
INDEX `idx_domain_purpose` (`domain_id`, `purpose`, `enabled`),
|
||||
INDEX `idx_zone_enabled` (`zone_id`, `enabled`),
|
||||
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`) 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,
|
||||
`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, ...)',
|
||||
`kek_label` VARCHAR(255) NOT NULL COMMENT 'Provider-specific KEK label/ID for this version',
|
||||
`status` VARCHAR(32) NOT NULL DEFAULT 'Active' COMMENT 'Active, Previous, Archived',
|
||||
`hsm_profile_id` BIGINT UNSIGNED COMMENT 'HSM profile where this KEK version is stored',
|
||||
`created` DATETIME NOT NULL COMMENT 'Creation timestamp',
|
||||
`removed` DATETIME COMMENT 'Removal timestamp for soft delete',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_uuid` (`uuid`),
|
||||
UNIQUE KEY `uk_kms_key_version` (`kms_key_id`, `version_number`, `removed`),
|
||||
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`) 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,
|
||||
`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',
|
||||
`zone_id` BIGINT UNSIGNED NOT NULL COMMENT 'Zone ID for zone-scoped keys',
|
||||
`wrapped_blob` VARBINARY(4096) NOT NULL COMMENT 'Encrypted DEK material',
|
||||
`created` DATETIME NOT NULL COMMENT 'Creation timestamp',
|
||||
`removed` DATETIME COMMENT 'Removal timestamp for soft delete',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_uuid` (`uuid`),
|
||||
INDEX `idx_kms_key_id` (`kms_key_id`, `removed`),
|
||||
INDEX `idx_kek_version_id` (`kek_version_id`, `removed`),
|
||||
INDEX `idx_zone_id` (`zone_id`, `removed`),
|
||||
CONSTRAINT `fk_kms_wrapped_key__kms_key_id` FOREIGN KEY (`kms_key_id`) REFERENCES `kms_keys`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_wrapped_key__kek_version_id` FOREIGN KEY (`kek_version_id`) REFERENCES `kms_kek_versions`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_kms_wrapped_key__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KMS wrapped encryption keys (DEKs) - references kms_keys for KEK metadata and kek_versions for specific version';
|
||||
|
||||
-- Add KMS key reference to volumes table (which KMS key was used)
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'kms_key_id', 'BIGINT UNSIGNED COMMENT ''KMS key ID used for volume encryption''');
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.volumes', 'fk_volumes__kms_key_id', '(kms_key_id)', '`kms_keys`(`id`)');
|
||||
|
||||
-- Add KMS wrapped key reference to volumes table
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'kms_wrapped_key_id', 'BIGINT UNSIGNED COMMENT ''KMS wrapped key ID for volume encryption''');
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.volumes', 'fk_volumes__kms_wrapped_key_id', '(kms_wrapped_key_id)', '`kms_wrapped_key`(`id`)');
|
||||
|
||||
-- KMS Database Provider KEK Objects (PKCS#11-like object storage)
|
||||
-- Stores KEKs for the database KMS provider in a PKCS#11-compatible format
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`kms_database_kek_objects` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Object handle (PKCS#11 CKA_HANDLE)',
|
||||
`uuid` VARCHAR(40) NOT NULL COMMENT 'UUID',
|
||||
-- PKCS#11 Object Class (CKA_CLASS)
|
||||
`object_class` VARCHAR(32) NOT NULL DEFAULT 'CKO_SECRET_KEY' COMMENT 'PKCS#11 object class (CKO_SECRET_KEY, CKO_PRIVATE_KEY, etc.)',
|
||||
-- PKCS#11 Label (CKA_LABEL) - human-readable identifier
|
||||
`label` VARCHAR(255) NOT NULL COMMENT 'PKCS#11 label (CKA_LABEL) - human-readable identifier',
|
||||
-- PKCS#11 ID (CKA_ID) - application-defined identifier
|
||||
`object_id` VARBINARY(64) COMMENT 'PKCS#11 object ID (CKA_ID) - application-defined identifier',
|
||||
-- Key Type (CKA_KEY_TYPE)
|
||||
`key_type` VARCHAR(32) NOT NULL DEFAULT 'CKK_AES' COMMENT 'PKCS#11 key type (CKK_AES, CKK_RSA, etc.)',
|
||||
-- Key Material (CKA_VALUE) - encrypted KEK material
|
||||
`key_material` VARBINARY(512) NOT NULL COMMENT 'PKCS#11 key value (CKA_VALUE) - encrypted KEK material',
|
||||
-- Key Attributes (PKCS#11 boolean attributes)
|
||||
`is_sensitive` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_SENSITIVE - key material is sensitive',
|
||||
`is_extractable` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'PKCS#11 CKA_EXTRACTABLE - key can be extracted',
|
||||
`is_token` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_TOKEN - object is on token (persistent)',
|
||||
`is_private` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_PRIVATE - object is private',
|
||||
`is_modifiable` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'PKCS#11 CKA_MODIFIABLE - object can be modified',
|
||||
`is_copyable` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'PKCS#11 CKA_COPYABLE - object can be copied',
|
||||
`is_destroyable` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_DESTROYABLE - object can be destroyed',
|
||||
`always_sensitive` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_ALWAYS_SENSITIVE - key was always sensitive',
|
||||
`never_extractable` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_NEVER_EXTRACTABLE - key was never extractable',
|
||||
-- Key Metadata
|
||||
`purpose` VARCHAR(32) NOT NULL COMMENT 'Key purpose (VOLUME_ENCRYPTION, TLS_CERT)',
|
||||
`key_bits` INT NOT NULL COMMENT 'Key size in bits (128, 192, 256)',
|
||||
`algorithm` VARCHAR(64) NOT NULL DEFAULT 'AES/GCM/NoPadding' COMMENT 'Encryption algorithm',
|
||||
-- Validity Dates (PKCS#11 CKA_START_DATE, CKA_END_DATE)
|
||||
`start_date` DATETIME COMMENT 'PKCS#11 CKA_START_DATE - key validity start',
|
||||
`end_date` DATETIME COMMENT 'PKCS#11 CKA_END_DATE - key validity end',
|
||||
-- Lifecycle
|
||||
`created` DATETIME NOT NULL COMMENT 'Creation timestamp',
|
||||
`last_used` DATETIME COMMENT 'Last usage timestamp',
|
||||
`removed` DATETIME COMMENT 'Removal timestamp for soft delete',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_uuid` (`uuid`),
|
||||
UNIQUE KEY `uk_label_removed` (`label`, `removed`),
|
||||
INDEX `idx_purpose_removed` (`purpose`, `removed`),
|
||||
INDEX `idx_key_type` (`key_type`, `removed`),
|
||||
INDEX `idx_object_class` (`object_class`, `removed`),
|
||||
INDEX `idx_created` (`created`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KMS Database Provider KEK Objects - PKCS#11-like object storage for KEKs';
|
||||
|
||||
--- Disable/enable NICs
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''Indicates whether the NIC is enabled or not'' ');
|
||||
|
||||
|
||||
@ -40,6 +40,10 @@ SELECT
|
||||
`volumes`.`chain_info` AS `chain_info`,
|
||||
`volumes`.`external_uuid` AS `external_uuid`,
|
||||
`volumes`.`encrypt_format` AS `encrypt_format`,
|
||||
`volumes`.`kms_key_id` AS `kms_key_id`,
|
||||
`kms_keys`.`uuid` AS `kms_key_uuid`,
|
||||
`kms_keys`.`name` AS `kms_key_name`,
|
||||
`volumes`.`kms_wrapped_key_id` AS `kms_wrapped_key_id`,
|
||||
`volumes`.`delete_protection` AS `delete_protection`,
|
||||
`account`.`id` AS `account_id`,
|
||||
`account`.`uuid` AS `account_uuid`,
|
||||
@ -116,7 +120,7 @@ SELECT
|
||||
`resource_tag_domain`.`uuid` AS `tag_domain_uuid`,
|
||||
`resource_tag_domain`.`name` AS `tag_domain_name`
|
||||
FROM
|
||||
((((((((((((((((((`volumes`
|
||||
(((((((((((((((((((`volumes`
|
||||
JOIN `account`ON
|
||||
((`volumes`.`account_id` = `account`.`id`)))
|
||||
JOIN `domain`ON
|
||||
@ -129,8 +133,10 @@ LEFT JOIN `vm_instance`ON
|
||||
((`volumes`.`instance_id` = `vm_instance`.`id`)))
|
||||
LEFT JOIN `user_vm`ON
|
||||
((`user_vm`.`id` = `vm_instance`.`id`)))
|
||||
LEFT JOIN `volume_store_ref`ON
|
||||
LEFT JOIN `volume_store_ref` ON
|
||||
((`volumes`.`id` = `volume_store_ref`.`volume_id`)))
|
||||
LEFT JOIN `kms_keys` ON
|
||||
((`volumes`.`kms_key_id` = `kms_keys`.`id`)))
|
||||
LEFT JOIN `service_offering`ON
|
||||
((`vm_instance`.`service_offering_id` = `service_offering`.`id`)))
|
||||
LEFT JOIN `disk_offering`ON
|
||||
|
||||
@ -790,9 +790,9 @@ public class AncientDataMotionStrategy implements DataMotionStrategy {
|
||||
private boolean anyVolumeRequiresEncryption(DataObject ... objects) {
|
||||
for (DataObject o : objects) {
|
||||
// this fails code smell for returning true twice, but it is more readable than combining all tests into one statement
|
||||
if (o instanceof VolumeInfo && ((VolumeInfo) o).getPassphraseId() != null) {
|
||||
if (o instanceof VolumeInfo && (((VolumeInfo) o).getPassphraseId() != null || ((VolumeInfo) o).getKmsKeyId() != null)) {
|
||||
return true;
|
||||
} else if (o instanceof SnapshotInfo && ((SnapshotInfo) o).getBaseVolume().getPassphraseId() != null) {
|
||||
} else if (o instanceof SnapshotInfo && (((SnapshotInfo) o).getBaseVolume().getPassphraseId() != null || ((SnapshotInfo) o).getBaseVolume().getKmsKeyId() != null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2592,7 +2592,11 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
||||
newVol.setLastPoolId(lastPoolId);
|
||||
newVol.setLastId(volume.getId());
|
||||
|
||||
if (volume.getPassphraseId() != null) {
|
||||
if (volume.getKmsKeyId() != null) {
|
||||
newVol.setKmsKeyId(volume.getKmsKeyId());
|
||||
newVol.setKmsWrappedKeyId(volume.getKmsWrappedKeyId());
|
||||
newVol.setEncryptFormat(volume.getEncryptFormat());
|
||||
} else if (volume.getPassphraseId() != null) {
|
||||
newVol.setPassphraseId(volume.getPassphraseId());
|
||||
newVol.setEncryptFormat(volume.getEncryptFormat());
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ import com.cloud.utils.db.Transaction;
|
||||
import com.cloud.utils.db.TransactionCallbackNoReturn;
|
||||
import com.cloud.utils.db.TransactionStatus;
|
||||
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.secret.dao.PassphraseDao;
|
||||
import org.apache.cloudstack.secret.PassphraseVO;
|
||||
import com.cloud.service.dao.ServiceOfferingDetailsDao;
|
||||
@ -47,6 +48,8 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao;
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
|
||||
import org.apache.cloudstack.storage.datastore.ObjectInDataStoreManager;
|
||||
@ -99,6 +102,10 @@ public class VolumeObject implements VolumeInfo {
|
||||
@Inject
|
||||
VolumeDataStoreDao volumeStoreDao;
|
||||
@Inject
|
||||
KMSManager kmsManager;
|
||||
@Inject
|
||||
KMSWrappedKeyDao kmsWrappedKeyDao;
|
||||
@Inject
|
||||
ObjectInDataStoreManager objectInStoreMgr;
|
||||
@Inject
|
||||
ResourceLimitService resourceLimitMgr;
|
||||
@ -926,6 +933,26 @@ public class VolumeObject implements VolumeInfo {
|
||||
volumeVO.setPassphraseId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getKmsKeyId() {
|
||||
return volumeVO.getKmsKeyId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKmsKeyId(Long id) {
|
||||
volumeVO.setKmsKeyId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getKmsWrappedKeyId() {
|
||||
return volumeVO.getKmsWrappedKeyId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKmsWrappedKeyId(Long id) {
|
||||
volumeVO.setKmsWrappedKeyId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes passphrase reference from underlying volume. Also removes the associated passphrase entry if it is the last user.
|
||||
*/
|
||||
@ -955,9 +982,29 @@ public class VolumeObject implements VolumeInfo {
|
||||
|
||||
/**
|
||||
* Looks up passphrase from underlying volume.
|
||||
* @return passphrase as bytes
|
||||
* Supports both legacy passphrase-based encryption and KMS-based encryption.
|
||||
* @return passphrase/DEK as base64-encoded bytes (UTF-8 bytes of base64 string)
|
||||
*/
|
||||
public byte[] getPassphrase() {
|
||||
// First check for KMS-encrypted volume
|
||||
if (volumeVO.getKmsWrappedKeyId() != null) {
|
||||
try {
|
||||
// Unwrap the DEK from KMS (returns raw binary bytes)
|
||||
byte[] dekBytes = kmsManager.unwrapKey(volumeVO.getKmsWrappedKeyId());
|
||||
// Base64-encode the DEK for consistency with legacy passphrases
|
||||
// and for use with qemu-img which expects base64 format
|
||||
String base64Dek = java.util.Base64.getEncoder().encodeToString(dekBytes);
|
||||
// Zeroize the raw DEK bytes
|
||||
java.util.Arrays.fill(dekBytes, (byte) 0);
|
||||
// Return UTF-8 bytes of the base64 string
|
||||
return base64Dek.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
} catch (KMSException e) {
|
||||
logger.error("Failed to unwrap KMS key for volume {}: {}", volumeVO, e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to legacy passphrase-based encryption
|
||||
PassphraseVO passphrase = passphraseDao.findById(volumeVO.getPassphraseId());
|
||||
if (passphrase != null) {
|
||||
return passphrase.getPassphrase();
|
||||
|
||||
@ -66,6 +66,7 @@ import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
|
||||
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
|
||||
import org.apache.cloudstack.framework.async.AsyncRpcContext;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.kms.KMSManager;
|
||||
import org.apache.cloudstack.secret.dao.PassphraseDao;
|
||||
import org.apache.cloudstack.storage.RemoteHostEndPoint;
|
||||
import org.apache.cloudstack.storage.command.CommandResult;
|
||||
@ -226,6 +227,9 @@ public class VolumeServiceImpl implements VolumeService {
|
||||
@Inject
|
||||
ClvmPoolManager clvmPoolManager;
|
||||
|
||||
@Inject
|
||||
private KMSManager kmsManager;
|
||||
|
||||
public VolumeServiceImpl() {
|
||||
}
|
||||
|
||||
@ -506,6 +510,13 @@ public class VolumeServiceImpl implements VolumeService {
|
||||
if (vo.getPassphraseId() != null) {
|
||||
vo.deletePassphrase();
|
||||
}
|
||||
if (vo.getKmsWrappedKeyId() != null) {
|
||||
try {
|
||||
kmsManager.deleteKMSWrappedKey(vo);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to delete KMS wrapped key for volume {}", vo, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (canVolumeBeRemoved(vo.getId())) {
|
||||
logger.info("Volume {} is not referred anywhere, remove it from volumes table", vo);
|
||||
@ -1758,6 +1769,11 @@ public class VolumeServiceImpl implements VolumeService {
|
||||
newVol.setPoolType(pool.getPoolType());
|
||||
newVol.setLastPoolId(lastPoolId);
|
||||
newVol.setPodId(pool.getPodId());
|
||||
if (volume.getKmsKeyId() != null) {
|
||||
newVol.setKmsKeyId(volume.getKmsKeyId());
|
||||
newVol.setKmsWrappedKeyId(volume.getKmsWrappedKeyId());
|
||||
newVol.setEncryptFormat(volume.getEncryptFormat());
|
||||
}
|
||||
if (volume.getPassphraseId() != null) {
|
||||
newVol.setPassphraseId(volume.getPassphraseId());
|
||||
newVol.setEncryptFormat(volume.getEncryptFormat());
|
||||
|
||||
46
framework/kms/pom.xml
Normal file
46
framework/kms/pom.xml
Normal file
@ -0,0 +1,46 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloud-framework-kms</artifactId>
|
||||
<name>Apache CloudStack Framework - Key Management Service</name>
|
||||
<description>Core KMS framework with provider-agnostic interfaces</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-framework</artifactId>
|
||||
<version>4.23.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-utils</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-config</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,181 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.framework.kms;
|
||||
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
/**
|
||||
* Exception class for KMS-related errors with structured error types
|
||||
* to enable proper retry logic and error handling.
|
||||
*/
|
||||
public class KMSException extends CloudRuntimeException {
|
||||
|
||||
/**
|
||||
* Error types for KMS operations to enable intelligent retry logic
|
||||
*/
|
||||
public enum ErrorType {
|
||||
CONNECTION_FAILED(true),
|
||||
/**
|
||||
* Authentication failed (e.g., incorrect PIN)
|
||||
*/
|
||||
AUTHENTICATION_FAILED(false),
|
||||
/**
|
||||
* Provider not initialized or unavailable
|
||||
*/
|
||||
PROVIDER_NOT_INITIALIZED(false),
|
||||
|
||||
/**
|
||||
* KEK not found in backend
|
||||
*/
|
||||
KEK_NOT_FOUND(false),
|
||||
|
||||
/**
|
||||
* KEK with given label already exists
|
||||
*/
|
||||
KEY_ALREADY_EXISTS(false),
|
||||
|
||||
/**
|
||||
* Invalid parameters provided
|
||||
*/
|
||||
INVALID_PARAMETER(false),
|
||||
|
||||
/**
|
||||
* Wrap/unwrap operation failed
|
||||
*/
|
||||
WRAP_UNWRAP_FAILED(true),
|
||||
|
||||
/**
|
||||
* KEK operation (create/delete) failed
|
||||
*/
|
||||
KEK_OPERATION_FAILED(true),
|
||||
|
||||
/**
|
||||
* Health check failed
|
||||
*/
|
||||
HEALTH_CHECK_FAILED(true),
|
||||
|
||||
/**
|
||||
* Transient network or communication error
|
||||
*/
|
||||
TRANSIENT_ERROR(true),
|
||||
|
||||
/**
|
||||
* Unknown error
|
||||
*/
|
||||
UNKNOWN(false);
|
||||
|
||||
private final boolean retryable;
|
||||
|
||||
ErrorType(boolean retryable) {
|
||||
this.retryable = retryable;
|
||||
}
|
||||
|
||||
public boolean isRetryable() {
|
||||
return retryable;
|
||||
}
|
||||
}
|
||||
|
||||
private final ErrorType errorType;
|
||||
|
||||
public KMSException(String message) {
|
||||
super(message);
|
||||
this.errorType = ErrorType.UNKNOWN;
|
||||
}
|
||||
|
||||
public KMSException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.errorType = ErrorType.UNKNOWN;
|
||||
}
|
||||
|
||||
public KMSException(ErrorType errorType, String message) {
|
||||
super(message);
|
||||
this.errorType = errorType;
|
||||
}
|
||||
|
||||
public KMSException(ErrorType errorType, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.errorType = errorType;
|
||||
}
|
||||
|
||||
public static KMSException providerNotInitialized(String details) {
|
||||
return new KMSException(ErrorType.PROVIDER_NOT_INITIALIZED,
|
||||
"KMS provider not initialized: " + details);
|
||||
}
|
||||
|
||||
public static KMSException kekNotFound(String kekId) {
|
||||
return new KMSException(ErrorType.KEK_NOT_FOUND,
|
||||
"KEK not found: " + kekId);
|
||||
}
|
||||
|
||||
public static KMSException keyAlreadyExists(String details) {
|
||||
return new KMSException(ErrorType.KEY_ALREADY_EXISTS,
|
||||
"Key already exists: " + details);
|
||||
}
|
||||
|
||||
public static KMSException invalidParameter(String details) {
|
||||
return new KMSException(ErrorType.INVALID_PARAMETER,
|
||||
"Invalid parameter: " + details);
|
||||
}
|
||||
|
||||
public static KMSException wrapUnwrapFailed(String details, Throwable cause) {
|
||||
return new KMSException(ErrorType.WRAP_UNWRAP_FAILED,
|
||||
"Wrap/unwrap operation failed: " + details, cause);
|
||||
}
|
||||
|
||||
public static KMSException wrapUnwrapFailed(String details) {
|
||||
return new KMSException(ErrorType.WRAP_UNWRAP_FAILED,
|
||||
"Wrap/unwrap operation failed: " + details);
|
||||
}
|
||||
|
||||
public static KMSException kekOperationFailed(String details, Throwable cause) {
|
||||
return new KMSException(ErrorType.KEK_OPERATION_FAILED,
|
||||
"KEK operation failed: " + details, cause);
|
||||
}
|
||||
|
||||
public static KMSException kekOperationFailed(String details) {
|
||||
return new KMSException(ErrorType.KEK_OPERATION_FAILED,
|
||||
"KEK operation failed: " + details);
|
||||
}
|
||||
|
||||
public static KMSException healthCheckFailed(String details, Throwable cause) {
|
||||
return new KMSException(ErrorType.HEALTH_CHECK_FAILED,
|
||||
"Health check failed: " + details, cause);
|
||||
}
|
||||
|
||||
public static KMSException transientError(String details, Throwable cause) {
|
||||
return new KMSException(ErrorType.TRANSIENT_ERROR,
|
||||
"Transient error: " + details, cause);
|
||||
}
|
||||
|
||||
public ErrorType getErrorType() {
|
||||
return errorType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KMSException{" +
|
||||
"errorType=" + errorType +
|
||||
", retryable=" + isRetryable() +
|
||||
", message='" + getMessage() + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public boolean isRetryable() {
|
||||
return errorType.isRetryable();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,255 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.framework.kms;
|
||||
|
||||
import com.cloud.utils.component.Adapter;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
|
||||
/**
|
||||
* Abstract provider contract for Key Management Service operations.
|
||||
* <p>
|
||||
* Implementations provide the cryptographic backend (HSM via PKCS#11, database, cloud KMS, etc.)
|
||||
* for secure key wrapping/unwrapping using envelope encryption.
|
||||
* <p>
|
||||
* Design principles:
|
||||
* - KEKs (Key Encryption Keys) never leave the secure backend
|
||||
* - DEKs (Data Encryption Keys) are wrapped by KEKs for storage
|
||||
* - Plaintext DEKs exist only transiently in memory during wrap/unwrap
|
||||
* - All operations are purpose-scoped to prevent key reuse
|
||||
* <p>
|
||||
* Thread-safety: Implementations must be thread-safe for concurrent operations.
|
||||
*/
|
||||
public interface KMSProvider extends Configurable, Adapter {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given HSM profile configuration key name refers
|
||||
* to a
|
||||
* sensitive value (PIN, password, secret, or private key) that must be
|
||||
* encrypted at
|
||||
* rest and masked in API responses.
|
||||
*
|
||||
* <p>
|
||||
* This is a shared naming-convention helper used by both KMS providers (when
|
||||
* loading/storing profile details) and the KMS manager (when building API
|
||||
* responses).
|
||||
*
|
||||
* @param key configuration key name (case-insensitive); null returns false
|
||||
* @return true if the key is considered sensitive
|
||||
*/
|
||||
static boolean isSensitiveKey(String key) {
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
return key.equalsIgnoreCase("pin") ||
|
||||
key.equalsIgnoreCase("password") ||
|
||||
key.toLowerCase().contains("secret") ||
|
||||
key.equalsIgnoreCase("private_key");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique name of this provider
|
||||
*
|
||||
* @return provider name (e.g., "database", "pkcs11")
|
||||
*/
|
||||
String getProviderName();
|
||||
|
||||
/**
|
||||
* Create a new Key Encryption Key (KEK) in the secure backend.
|
||||
* Delegates to {@link #createKek(KeyPurpose, String, int, Long)} with null profile ID.
|
||||
*
|
||||
* @param purpose the purpose/scope for this KEK
|
||||
* @param label human-readable label for the KEK (must be unique within purpose)
|
||||
* @param keyBits key size in bits (typically 128, 192, or 256)
|
||||
* @return the KEK identifier (label or handle) for later reference
|
||||
* @throws KMSException if KEK creation fails
|
||||
*/
|
||||
default String createKek(KeyPurpose purpose, String label, int keyBits) throws KMSException {
|
||||
return createKek(purpose, label, keyBits, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Key Encryption Key (KEK) in the secure backend with explicit HSM profile.
|
||||
*
|
||||
* @param purpose the purpose/scope for this KEK
|
||||
* @param label human-readable label for the KEK (must be unique within purpose)
|
||||
* @param keyBits key size in bits (typically 128, 192, or 256)
|
||||
* @param hsmProfileId optional HSM profile ID to create the KEK in (null for auto-resolution/default)
|
||||
* @return the KEK identifier (label or handle) for later reference
|
||||
* @throws KMSException if KEK creation fails
|
||||
*/
|
||||
String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Delete a KEK from the secure backend.
|
||||
* WARNING: This will make all DEKs wrapped by this KEK unrecoverable.
|
||||
*
|
||||
* @param kekId the KEK identifier to delete
|
||||
* @throws KMSException if deletion fails or KEK not found
|
||||
*/
|
||||
void deleteKek(String kekId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Validates the configuration details for this provider before saving an HSM
|
||||
* profile.
|
||||
* Implementations should override this to perform provider-specific validation.
|
||||
*
|
||||
* @param details the configuration details to validate
|
||||
* @throws KMSException if validation fails
|
||||
*/
|
||||
default void validateProfileConfig(java.util.Map<String, String> details) throws KMSException {
|
||||
// default no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a KEK exists and is accessible
|
||||
*
|
||||
* @param kekId the KEK identifier to check
|
||||
* @return true if KEK is available
|
||||
* @throws KMSException if check fails
|
||||
*/
|
||||
boolean isKekAvailable(String kekId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Wrap (encrypt) a plaintext Data Encryption Key with a KEK.
|
||||
* Delegates to {@link #wrapKey(byte[], KeyPurpose, String, Long)} with null profile ID.
|
||||
*
|
||||
* @param plainDek the plaintext DEK to wrap (caller must zeroize after call)
|
||||
* @param purpose the intended purpose of this DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @return WrappedKey containing the encrypted DEK and metadata
|
||||
* @throws KMSException if wrapping fails or KEK not found
|
||||
*/
|
||||
default WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel) throws KMSException {
|
||||
return wrapKey(plainDek, purpose, kekLabel, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap (encrypt) a plaintext Data Encryption Key with a KEK using explicit HSM profile.
|
||||
*
|
||||
* @param plainDek the plaintext DEK to wrap (caller must zeroize after call)
|
||||
* @param purpose the intended purpose of this DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return WrappedKey containing the encrypted DEK and metadata
|
||||
* @throws KMSException if wrapping fails or KEK not found
|
||||
*/
|
||||
WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Unwrap (decrypt) a wrapped DEK to obtain the plaintext key.
|
||||
* Delegates to {@link #unwrapKey(WrappedKey, Long)} with null profile ID.
|
||||
* <p>
|
||||
* SECURITY: Caller MUST zeroize the returned byte array after use
|
||||
*
|
||||
* @param wrappedKey the wrapped key to decrypt
|
||||
* @return plaintext DEK (caller must zeroize!)
|
||||
* @throws KMSException if unwrapping fails or KEK not found
|
||||
*/
|
||||
default byte[] unwrapKey(WrappedKey wrappedKey) throws KMSException {
|
||||
return unwrapKey(wrappedKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap (decrypt) a wrapped DEK to obtain the plaintext key using explicit HSM profile.
|
||||
* <p>
|
||||
* SECURITY: Caller MUST zeroize the returned byte array after use
|
||||
*
|
||||
* @param wrappedKey the wrapped key to decrypt
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return plaintext DEK (caller must zeroize!)
|
||||
* @throws KMSException if unwrapping fails or KEK not found
|
||||
*/
|
||||
byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Generate a new random DEK and immediately wrap it with a KEK.
|
||||
* Delegates to {@link #generateAndWrapDek(KeyPurpose, String, int, Long)} with null profile ID.
|
||||
* (convenience method combining generation + wrapping)
|
||||
*
|
||||
* @param purpose the intended purpose of the new DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @param keyBits DEK size in bits (typically 128, 192, or 256)
|
||||
* @return WrappedKey containing the newly generated and wrapped DEK
|
||||
* @throws KMSException if generation or wrapping fails
|
||||
*/
|
||||
default WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits) throws KMSException {
|
||||
return generateAndWrapDek(purpose, kekLabel, keyBits, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new random DEK and immediately wrap it with a KEK using explicit HSM profile.
|
||||
* (convenience method combining generation + wrapping)
|
||||
*
|
||||
* @param purpose the intended purpose of the new DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @param keyBits DEK size in bits (typically 128, 192, or 256)
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return WrappedKey containing the newly generated and wrapped DEK
|
||||
* @throws KMSException if generation or wrapping fails
|
||||
*/
|
||||
WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits,
|
||||
Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Rewrap a DEK with a different KEK (used during key rotation).
|
||||
* Delegates to {@link #rewrapKey(WrappedKey, String, Long)} with null profile ID.
|
||||
* This unwraps with the old KEK and wraps with the new KEK without exposing the plaintext DEK.
|
||||
*
|
||||
* @param oldWrappedKey the currently wrapped key
|
||||
* @param newKekLabel the label of the new KEK to wrap with
|
||||
* @return new WrappedKey encrypted with the new KEK
|
||||
* @throws KMSException if rewrapping fails
|
||||
*/
|
||||
default WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel) throws KMSException {
|
||||
return rewrapKey(oldWrappedKey, newKekLabel, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrap a DEK with a different KEK (used during key rotation) using explicit target HSM profile.
|
||||
* This unwraps with the old KEK and wraps with the new KEK without exposing the plaintext DEK.
|
||||
*
|
||||
* @param oldWrappedKey the currently wrapped key
|
||||
* @param newKekLabel the label of the new KEK to wrap with
|
||||
* @param targetHsmProfileId optional target HSM profile ID to wrap with (null for auto-resolution/default)
|
||||
* @return new WrappedKey encrypted with the new KEK
|
||||
* @throws KMSException if rewrapping fails
|
||||
*/
|
||||
WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel, Long targetHsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Perform health check on the provider backend
|
||||
*
|
||||
* @return true if provider is healthy and operational
|
||||
* @throws KMSException if health check fails with critical error
|
||||
*/
|
||||
boolean healthCheck() throws KMSException;
|
||||
|
||||
/**
|
||||
* Invalidates any cached state (config, sessions) associated with the given HSM profile.
|
||||
* Must be called after an HSM profile is updated or deleted so that the next operation
|
||||
* re-reads the profile details from the database instead of using stale cached values.
|
||||
*
|
||||
* <p>Providers that do not cache per-profile state (e.g. the database provider) can
|
||||
* leave this as a no-op.
|
||||
*
|
||||
* @param profileId the HSM profile ID whose cache should be evicted
|
||||
*/
|
||||
default void invalidateProfileCache(Long profileId) {
|
||||
// no-op for providers that don't cache per-profile state
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.framework.kms;
|
||||
|
||||
/**
|
||||
* Defines the purpose/usage scope for cryptographic keys in the KMS system.
|
||||
* This enables proper key segregation and prevents key reuse across different contexts.
|
||||
*/
|
||||
public enum KeyPurpose {
|
||||
/**
|
||||
* Keys used for encrypting VM disk volumes (LUKS, encrypted storage)
|
||||
*/
|
||||
VOLUME_ENCRYPTION("volume", "Volume disk encryption keys"),
|
||||
|
||||
/**
|
||||
* Keys used for protecting TLS certificate private keys
|
||||
*/
|
||||
TLS_CERT("tls", "TLS certificate private keys");
|
||||
|
||||
private final String name;
|
||||
private final String description;
|
||||
|
||||
KeyPurpose(String name, String description) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string name to KeyPurpose enum
|
||||
*
|
||||
* @param name the string representation of the purpose
|
||||
* @return matching KeyPurpose
|
||||
* @throws IllegalArgumentException if no matching purpose found
|
||||
*/
|
||||
public static KeyPurpose fromString(String name) {
|
||||
for (KeyPurpose purpose : KeyPurpose.values()) {
|
||||
if (purpose.getName().equalsIgnoreCase(name)) {
|
||||
return purpose;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown KeyPurpose: " + name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a globally unique, collision-resistant KEK label with context
|
||||
*
|
||||
* @param domainId the domain ID associated with this key
|
||||
* @param accountId the account ID associated with this key
|
||||
* @param uuid the unique identifier of the key entity
|
||||
* @param version the version number of the key
|
||||
* @return formatted KEK label (e.g., "volume-kek-1-2-a8054d8f-...-1")
|
||||
*/
|
||||
public String generateKekLabel(long domainId, long accountId, String uuid, int version) {
|
||||
return name + "-kek-" + domainId + "-" + accountId + "-" + uuid.replace("-", "") + "-v" + version;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.framework.kms;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Immutable Data Transfer Object representing an encrypted (wrapped) Data Encryption Key.
|
||||
* The wrapped key material contains the DEK encrypted by a Key Encryption Key (KEK)
|
||||
* stored in a secure backend (HSM, database, etc.).
|
||||
* <p>
|
||||
* This follows the envelope encryption pattern:
|
||||
* - DEK: encrypts actual data (e.g., disk volume)
|
||||
* - KEK: encrypts the DEK (never leaves secure storage)
|
||||
* - Wrapped Key: DEK encrypted by KEK, safe to store in database
|
||||
*/
|
||||
public class WrappedKey {
|
||||
private final String uuid;
|
||||
private final String kekId;
|
||||
private final KeyPurpose purpose;
|
||||
private final String algorithm;
|
||||
private final byte[] wrappedKeyMaterial;
|
||||
private final String providerName;
|
||||
private final Date created;
|
||||
private final Long zoneId;
|
||||
|
||||
/**
|
||||
* Create a new WrappedKey instance
|
||||
*
|
||||
* @param kekId ID/label of the KEK used to wrap this key
|
||||
* @param purpose the intended use of this key
|
||||
* @param algorithm encryption algorithm (e.g., "AES/GCM/NoPadding")
|
||||
* @param wrappedKeyMaterial the encrypted DEK blob
|
||||
* @param providerName name of the KMS provider that created this key
|
||||
* @param created timestamp when key was wrapped
|
||||
* @param zoneId optional zone ID for zone-scoped keys
|
||||
*/
|
||||
public WrappedKey(String kekId, KeyPurpose purpose, String algorithm,
|
||||
byte[] wrappedKeyMaterial, String providerName,
|
||||
Date created, Long zoneId) {
|
||||
this(null, kekId, purpose, algorithm, wrappedKeyMaterial, providerName, created, zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for database-loaded keys with ID
|
||||
*/
|
||||
public WrappedKey(String uuid, String kekId, KeyPurpose purpose, String algorithm,
|
||||
byte[] wrappedKeyMaterial, String providerName,
|
||||
Date created, Long zoneId) {
|
||||
this.uuid = uuid;
|
||||
this.kekId = Objects.requireNonNull(kekId, "kekId cannot be null");
|
||||
this.purpose = Objects.requireNonNull(purpose, "purpose cannot be null");
|
||||
this.algorithm = Objects.requireNonNull(algorithm, "algorithm cannot be null");
|
||||
this.providerName = providerName;
|
||||
|
||||
if (wrappedKeyMaterial == null || wrappedKeyMaterial.length == 0) {
|
||||
throw new IllegalArgumentException("wrappedKeyMaterial cannot be null or empty");
|
||||
}
|
||||
this.wrappedKeyMaterial = Arrays.copyOf(wrappedKeyMaterial, wrappedKeyMaterial.length);
|
||||
|
||||
this.created = created != null ? new Date(created.getTime()) : new Date();
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getKekId() {
|
||||
return kekId;
|
||||
}
|
||||
|
||||
public KeyPurpose getPurpose() {
|
||||
return purpose;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get wrapped key material. Returns a defensive copy to prevent modification.
|
||||
* Caller is responsible for zeroizing the returned array after use.
|
||||
*/
|
||||
public byte[] getWrappedKeyMaterial() {
|
||||
return Arrays.copyOf(wrappedKeyMaterial, wrappedKeyMaterial.length);
|
||||
}
|
||||
|
||||
public String getProviderName() {
|
||||
return providerName;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created != null ? new Date(created.getTime()) : null;
|
||||
}
|
||||
|
||||
public Long getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WrappedKey{" +
|
||||
"uuid='" + uuid + '\'' +
|
||||
", kekId='" + kekId + '\'' +
|
||||
", purpose=" + purpose +
|
||||
", algorithm='" + algorithm + '\'' +
|
||||
", providerName='" + providerName + '\'' +
|
||||
", materialLength=" + (wrappedKeyMaterial != null ? wrappedKeyMaterial.length : 0) +
|
||||
", created=" + created +
|
||||
", zoneId=" + zoneId +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -54,6 +54,7 @@
|
||||
<module>extensions</module>
|
||||
<module>ipc</module>
|
||||
<module>jobs</module>
|
||||
<module>kms</module>
|
||||
<module>managed-context</module>
|
||||
<module>quota</module>
|
||||
<module>rest</module>
|
||||
|
||||
@ -188,7 +188,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
hosts = hosts.stream().filter(host -> !constraints.antiAffinityOccupiedHosts.contains(host.getId())).collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(hosts)) {
|
||||
String msg = String.format("Cannot find capacity for Kubernetes cluster: host anti-affinity requires each VM on a separate host, " +
|
||||
"but all %d available hosts in zone %s are already occupied by existing cluster VMs",
|
||||
"but all %d available hosts in zone %s are already occupied by existing cluster VMs",
|
||||
constraints.antiAffinityOccupiedHosts.size(), zone.getName());
|
||||
throw new InsufficientServerCapacityException(msg, DataCenter.class, zone.getId());
|
||||
}
|
||||
@ -198,8 +198,8 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
}
|
||||
|
||||
protected DeployDestination plan(final long nodesCount, final DataCenter zone, final ServiceOffering offering,
|
||||
final Long domainId, final Long accountId, final Hypervisor.HypervisorType hypervisorType,
|
||||
CPU.CPUArch arch, KubernetesClusterNodeType nodeType) throws InsufficientServerCapacityException {
|
||||
final Long domainId, final Long accountId, final Hypervisor.HypervisorType hypervisorType,
|
||||
CPU.CPUArch arch, KubernetesClusterNodeType nodeType) throws InsufficientServerCapacityException {
|
||||
final int cpu_requested = offering.getCpu() * offering.getSpeed();
|
||||
final long ram_requested = offering.getRamSize() * 1024L * 1024L;
|
||||
boolean useDedicatedHosts = false;
|
||||
@ -295,7 +295,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
String msg;
|
||||
if (affinityConstraints.hasHostAntiAffinity) {
|
||||
msg = String.format("Cannot find enough capacity for Kubernetes cluster (requested cpu=%d memory=%s) with offering: %s. " +
|
||||
"Host anti-affinity requires %d separate hosts but not enough suitable hosts are available in zone %s",
|
||||
"Host anti-affinity requires %d separate hosts but not enough suitable hosts are available in zone %s",
|
||||
cpu_requested * nodesCount, toHumanReadableSize(ram_requested * nodesCount), offering.getName(),
|
||||
nodesCount, zone.getName());
|
||||
} else {
|
||||
@ -399,7 +399,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
}
|
||||
|
||||
protected List<UserVm> provisionKubernetesClusterNodeVms(final long nodeCount, final int offset,
|
||||
final String controlIpAddress, final Long domainId, final Long accountId) throws ManagementServerException,
|
||||
final String controlIpAddress, final Long domainId, final Long accountId) throws ManagementServerException,
|
||||
ResourceUnavailableException, InsufficientCapacityException {
|
||||
List<UserVm> nodes = new ArrayList<>();
|
||||
for (int i = offset + 1; i <= nodeCount; i++) {
|
||||
@ -468,12 +468,14 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
nodeVm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, workerNodeTemplate, networkIds, securityGroupIds, owner,
|
||||
hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs,
|
||||
null, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null,
|
||||
null, true, null, UserVmManager.CKS_NODE, null, null);
|
||||
null, true, null, null, UserVmManager.CKS_NODE, null, null);
|
||||
} else {
|
||||
nodeVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, workerNodeTemplate, networkIds, owner,
|
||||
hostName, hostName, null, null, null, null,
|
||||
Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs,
|
||||
null, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
|
||||
null, addrs, null, null, affinityGroupIds, customParameterMap,
|
||||
null, null, null, null, true,
|
||||
UserVmManager.CKS_NODE, null, null, null, null);
|
||||
}
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Created node VM : {}, {} in the Kubernetes cluster : {}", hostName, nodeVm, kubernetesCluster.getName());
|
||||
@ -504,7 +506,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
}
|
||||
|
||||
protected void provisionPublicIpPortForwardingRule(IpAddress publicIp, Network network, Account account,
|
||||
final long vmId, final int sourcePort, final int destPort) throws NetworkRuleConflictException, ResourceUnavailableException {
|
||||
final long vmId, final int sourcePort, final int destPort) throws NetworkRuleConflictException, ResourceUnavailableException {
|
||||
final long publicIpId = publicIp.getId();
|
||||
final long networkId = network.getId();
|
||||
final long accountId = account.getId();
|
||||
@ -543,7 +545,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
* @throws NetworkRuleConflictException
|
||||
*/
|
||||
protected void provisionSshPortForwardingRules(IpAddress publicIp, Network network, Account account,
|
||||
List<Long> clusterVMIds, Map<Long, Integer> vmIdPortMap) throws ResourceUnavailableException,
|
||||
List<Long> clusterVMIds, Map<Long, Integer> vmIdPortMap) throws ResourceUnavailableException,
|
||||
NetworkRuleConflictException {
|
||||
if (!CollectionUtils.isEmpty(clusterVMIds)) {
|
||||
int defaultNodesCount = clusterVMIds.size() - vmIdPortMap.size();
|
||||
@ -566,7 +568,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
Integer startPort = firewallRule.getSourcePortStart();
|
||||
Integer endPort = firewallRule.getSourcePortEnd();
|
||||
if (startPort != null && startPort == CLUSTER_API_PORT &&
|
||||
endPort != null && endPort == CLUSTER_API_PORT) {
|
||||
endPort != null && endPort == CLUSTER_API_PORT) {
|
||||
rule = firewallRule;
|
||||
firewallService.revokeIngressFwRule(firewallRule.getId(), true);
|
||||
logger.debug("The API firewall rule [%s] with the id [%s] was revoked",firewallRule.getName(),firewallRule.getId());
|
||||
@ -613,7 +615,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
}
|
||||
|
||||
protected void removePortForwardingRules(final IpAddress publicIp, final Network network, final Account account, int startPort, int endPort)
|
||||
throws ResourceUnavailableException {
|
||||
throws ResourceUnavailableException {
|
||||
List<PortForwardingRuleVO> pfRules = portForwardingRulesDao.listByNetwork(network.getId());
|
||||
for (PortForwardingRuleVO pfRule : pfRules) {
|
||||
if (startPort <= pfRule.getSourcePortStart() && pfRule.getSourcePortStart() <= endPort) {
|
||||
@ -626,10 +628,10 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
}
|
||||
|
||||
protected void removeLoadBalancingRule(final IpAddress publicIp, final Network network,
|
||||
final Account account) throws ResourceUnavailableException {
|
||||
final Account account) throws ResourceUnavailableException {
|
||||
List<LoadBalancerVO> loadBalancerRules = loadBalancerDao.listByIpAddress(publicIp.getId());
|
||||
loadBalancerRules.stream().filter(lbRules -> lbRules.getNetworkId() == network.getId() && lbRules.getAccountId() == account.getId() && lbRules.getSourcePortStart() == CLUSTER_API_PORT
|
||||
&& lbRules.getSourcePortEnd() == CLUSTER_API_PORT).forEach(lbRule -> {
|
||||
&& lbRules.getSourcePortEnd() == CLUSTER_API_PORT).forEach(lbRule -> {
|
||||
lbService.deleteLoadBalancerRule(lbRule.getId(), true);
|
||||
logger.debug("The load balancing rule with the Id: {} was removed",lbRule.getId());
|
||||
});
|
||||
@ -665,12 +667,12 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
IllegalAccessException, ResourceUnavailableException {
|
||||
List<NetworkACLItemVO> aclItems = networkACLItemDao.listByACL(network.getNetworkACLId());
|
||||
aclItems = aclItems.stream().filter(networkACLItem -> (networkACLItem.getProtocol() != null &&
|
||||
networkACLItem.getProtocol().equals("TCP") &&
|
||||
networkACLItem.getSourcePortStart() != null &&
|
||||
networkACLItem.getSourcePortStart().equals(startPort) &&
|
||||
networkACLItem.getSourcePortEnd() != null &&
|
||||
networkACLItem.getSourcePortEnd().equals(endPort) &&
|
||||
networkACLItem.getAction().equals(NetworkACLItem.Action.Allow)))
|
||||
networkACLItem.getProtocol().equals("TCP") &&
|
||||
networkACLItem.getSourcePortStart() != null &&
|
||||
networkACLItem.getSourcePortStart().equals(startPort) &&
|
||||
networkACLItem.getSourcePortEnd() != null &&
|
||||
networkACLItem.getSourcePortEnd().equals(endPort) &&
|
||||
networkACLItem.getAction().equals(NetworkACLItem.Action.Allow)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (NetworkACLItemVO aclItem : aclItems) {
|
||||
@ -679,7 +681,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
}
|
||||
|
||||
protected void provisionLoadBalancerRule(final IpAddress publicIp, final Network network,
|
||||
final Account account, final List<Long> clusterVMIds, final int port) throws NetworkRuleConflictException,
|
||||
final Account account, final List<Long> clusterVMIds, final int port) throws NetworkRuleConflictException,
|
||||
InsufficientAddressCapacityException {
|
||||
LoadBalancer lb = lbService.createPublicLoadBalancerRule(null, "api-lb", "LB rule for API access",
|
||||
port, port, port, port,
|
||||
@ -879,11 +881,11 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
}
|
||||
|
||||
protected KubernetesClusterVO updateKubernetesClusterEntry(final Long cores, final Long memory, final Long size,
|
||||
final Long serviceOfferingId, final Boolean autoscaleEnabled,
|
||||
final Long minSize, final Long maxSize,
|
||||
final KubernetesClusterNodeType nodeType,
|
||||
final boolean updateNodeOffering,
|
||||
final boolean updateClusterOffering) {
|
||||
final Long serviceOfferingId, final Boolean autoscaleEnabled,
|
||||
final Long minSize, final Long maxSize,
|
||||
final KubernetesClusterNodeType nodeType,
|
||||
final boolean updateNodeOffering,
|
||||
final boolean updateClusterOffering) {
|
||||
return Transaction.execute((TransactionCallback<KubernetesClusterVO>) status -> {
|
||||
KubernetesClusterVO updatedCluster = kubernetesClusterDao.createForUpdate(kubernetesCluster.getId());
|
||||
|
||||
@ -941,9 +943,9 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
try {
|
||||
if (enable) {
|
||||
String command = String.format("sudo /opt/bin/autoscale-kube-cluster -i %s -e -M %d -m %d",
|
||||
kubernetesCluster.getUuid(), maxSize, minSize);
|
||||
kubernetesCluster.getUuid(), maxSize, minSize);
|
||||
Pair<Boolean, String> result = SshHelper.sshExecute(publicIpAddress, sshPort, getControlNodeLoginUser(),
|
||||
pkFile, null, command, 10000, 10000, 60000);
|
||||
pkFile, null, command, 10000, 10000, 60000);
|
||||
|
||||
// Maybe the file isn't present. Try and copy it
|
||||
if (!result.first()) {
|
||||
@ -953,12 +955,12 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
|
||||
if (!createCloudStackSecret(keys)) {
|
||||
logTransitStateAndThrow(Level.ERROR, String.format("Failed to setup keys for Kubernetes cluster %s",
|
||||
kubernetesCluster.getName()), kubernetesCluster.getId(), KubernetesCluster.Event.OperationFailed);
|
||||
kubernetesCluster.getName()), kubernetesCluster.getId(), KubernetesCluster.Event.OperationFailed);
|
||||
}
|
||||
|
||||
// If at first you don't succeed ...
|
||||
result = SshHelper.sshExecute(publicIpAddress, sshPort, getControlNodeLoginUser(),
|
||||
pkFile, null, command, 10000, 10000, 60000);
|
||||
pkFile, null, command, 10000, 10000, 60000);
|
||||
if (!result.first()) {
|
||||
throw new CloudRuntimeException(result.second());
|
||||
}
|
||||
@ -966,7 +968,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
|
||||
updateKubernetesClusterEntry(true, minSize, maxSize);
|
||||
} else {
|
||||
Pair<Boolean, String> result = SshHelper.sshExecute(publicIpAddress, sshPort, getControlNodeLoginUser(),
|
||||
pkFile, null, String.format("sudo /opt/bin/autoscale-kube-cluster -d"),
|
||||
pkFile, null, String.format("sudo /opt/bin/autoscale-kube-cluster -d"),
|
||||
10000, 10000, 60000);
|
||||
if (!result.first()) {
|
||||
throw new CloudRuntimeException(result.second());
|
||||
|
||||
@ -142,8 +142,8 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
|
||||
}
|
||||
|
||||
private Pair<String, String> getKubernetesControlNodeConfig(final String controlNodeIp, final String serverIp,
|
||||
final List<Network.IpAddresses> etcdIps, final String hostName, final boolean haSupported,
|
||||
final boolean ejectIso, final boolean externalCni, final boolean setupCsi) throws IOException {
|
||||
final List<Network.IpAddresses> etcdIps, final String hostName, final boolean haSupported,
|
||||
final boolean ejectIso, final boolean externalCni, final boolean setupCsi) throws IOException {
|
||||
String k8sControlNodeConfig = readK8sConfigFile("/conf/k8s-control-node.yml");
|
||||
final String apiServerCert = "{{ k8s_control_node.apiserver.crt }}";
|
||||
final String apiServerKey = "{{ k8s_control_node.apiserver.key }}";
|
||||
@ -273,19 +273,21 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
|
||||
List<Long> affinityGroupIds = getMergedAffinityGroupIds(CONTROL, domainId, accountId);
|
||||
String userDataDetails = kubernetesCluster.getCniConfigDetails();
|
||||
if (kubernetesCluster.getSecurityGroupId() != null &&
|
||||
networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds,
|
||||
List.of(kubernetesCluster.getSecurityGroupId()))) {
|
||||
networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds,
|
||||
List.of(kubernetesCluster.getSecurityGroupId()))) {
|
||||
List<Long> securityGroupIds = new ArrayList<>();
|
||||
securityGroupIds.add(kubernetesCluster.getSecurityGroupId());
|
||||
controlVm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, securityGroupIds, owner,
|
||||
hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, userDataId, userDataDetails, keypairs,
|
||||
hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, userDataId, userDataDetails, keypairs,
|
||||
requestedIps, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null,
|
||||
null, true, null, UserVmManager.CKS_NODE, null, null);
|
||||
null, true, null, null, UserVmManager.CKS_NODE, null, null);
|
||||
} else {
|
||||
controlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, owner,
|
||||
hostName, hostName, null, null, null, null,
|
||||
Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, userDataId, userDataDetails, keypairs,
|
||||
requestedIps, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
|
||||
requestedIps, addrs, null, null, affinityGroupIds, customParameterMap,
|
||||
null, null, null, null, true,
|
||||
UserVmManager.CKS_NODE, null, null, null, null);
|
||||
}
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Created control VM: {}, {} in the Kubernetes cluster: {}", controlVm, hostName, kubernetesCluster);
|
||||
@ -372,7 +374,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
|
||||
}
|
||||
|
||||
private String getEtcdNodeConfig(final List<String> ipAddresses, final List<String> hostnames, final int etcdNodeIndex,
|
||||
final boolean ejectIso) throws IOException {
|
||||
final boolean ejectIso) throws IOException {
|
||||
String k8sEtcdNodeConfig = readK8sConfigFile("/conf/etcd-node.yml");
|
||||
final String sshPubKey = "{{ k8s.ssh.pub.key }}";
|
||||
final String ejectIsoKey = "{{ k8s.eject.iso }}";
|
||||
@ -406,7 +408,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
|
||||
}
|
||||
|
||||
private UserVm createKubernetesAdditionalControlNode(final String joinIp, final int additionalControlNodeInstance,
|
||||
final Long domainId, final Long accountId) throws ManagementServerException,
|
||||
final Long domainId, final Long accountId) throws ManagementServerException,
|
||||
ResourceUnavailableException, InsufficientCapacityException {
|
||||
UserVm additionalControlVm = null;
|
||||
DataCenter zone = dataCenterDao.findById(kubernetesCluster.getZoneId());
|
||||
@ -439,19 +441,21 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
|
||||
|
||||
List<Long> affinityGroupIds = getMergedAffinityGroupIds(CONTROL, domainId, accountId);
|
||||
if (kubernetesCluster.getSecurityGroupId() != null &&
|
||||
networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds,
|
||||
List.of(kubernetesCluster.getSecurityGroupId()))) {
|
||||
networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds,
|
||||
List.of(kubernetesCluster.getSecurityGroupId()))) {
|
||||
List<Long> securityGroupIds = new ArrayList<>();
|
||||
securityGroupIds.add(kubernetesCluster.getSecurityGroupId());
|
||||
additionalControlVm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, securityGroupIds, owner,
|
||||
hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs,
|
||||
null, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null,
|
||||
null, true, null, UserVmManager.CKS_NODE, null, null);
|
||||
null, true, null, null, UserVmManager.CKS_NODE, null, null);
|
||||
} else {
|
||||
additionalControlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, owner,
|
||||
hostName, hostName, null, null, null, null,
|
||||
Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs,
|
||||
null, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
|
||||
null, addrs, null, null, affinityGroupIds, customParameterMap,
|
||||
null, null, null, null, true,
|
||||
UserVmManager.CKS_NODE, null, null, null, null);
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
@ -488,12 +492,14 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
|
||||
etcdNode = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, etcdTemplate, networkIds, securityGroupIds, owner,
|
||||
hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs,
|
||||
Map.of(kubernetesCluster.getNetworkId(), requestedIps.get(etcdNodeIndex)), addrs, null, null, affinityGroupIds, customParameterMap, null, null, null,
|
||||
null, true, null, null, null, null);
|
||||
null, true, null, null, null, null, null);
|
||||
} else {
|
||||
etcdNode = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, etcdTemplate, networkIds, owner,
|
||||
hostName, hostName, null, null, null, null,
|
||||
Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs,
|
||||
Map.of(kubernetesCluster.getNetworkId(), requestedIps.get(etcdNodeIndex)), addrs, null, null, affinityGroupIds, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
|
||||
Map.of(kubernetesCluster.getNetworkId(), requestedIps.get(etcdNodeIndex)), addrs, null, null,
|
||||
affinityGroupIds, customParameterMap, null, null, null, null,
|
||||
true, UserVmManager.CKS_NODE, null, null, null, null);
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
@ -503,7 +509,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
|
||||
}
|
||||
|
||||
private Pair<UserVm, String> provisionKubernetesClusterControlVm(final Network network, final String publicIpAddress, final List<Network.IpAddresses> etcdIps,
|
||||
final Long domainId, final Long accountId, Long asNumber) throws
|
||||
final Long domainId, final Long accountId, Long asNumber) throws
|
||||
ManagementServerException, InsufficientCapacityException, ResourceUnavailableException {
|
||||
UserVm k8sControlVM = null;
|
||||
Pair<UserVm, String> k8sControlVMAndControlIP;
|
||||
@ -525,7 +531,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
|
||||
}
|
||||
|
||||
private List<UserVm> provisionKubernetesClusterAdditionalControlVms(final String controlIpAddress, final Long domainId,
|
||||
final Long accountId) throws
|
||||
final Long accountId) throws
|
||||
InsufficientCapacityException, ManagementServerException, ResourceUnavailableException {
|
||||
List<UserVm> additionalControlVms = new ArrayList<>();
|
||||
if (kubernetesCluster.getControlNodeCount() > 1) {
|
||||
@ -762,7 +768,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
|
||||
}
|
||||
publicIpAddress = publicIpSshPort.first();
|
||||
if (StringUtils.isEmpty(publicIpAddress) &&
|
||||
(!manager.isDirectAccess(network) || kubernetesCluster.getControlNodeCount() > 1)) { // Shared network, single-control node cluster won't have an IP yet
|
||||
(!manager.isDirectAccess(network) || kubernetesCluster.getControlNodeCount() > 1)) { // Shared network, single-control node cluster won't have an IP yet
|
||||
logTransitStateAndThrow(Level.ERROR, String.format("Failed to start Kubernetes cluster : %s as no public IP found for the cluster" , kubernetesCluster.getName()), kubernetesCluster.getId(), KubernetesCluster.Event.CreateFailed);
|
||||
}
|
||||
// Allow account creating the kubernetes cluster to access systemVM template
|
||||
|
||||
@ -1016,7 +1016,7 @@ public class ServerAdapter extends ManagerBase {
|
||||
Volume volume;
|
||||
try {
|
||||
volume = volumeApiService.allocVolume(serviceAccount.getId(), pool.getDataCenterId(), diskOfferingId, null,
|
||||
null, name, sizeInGb, null, null, null, null);
|
||||
null, name, sizeInGb, null, null, null, null, null);
|
||||
} catch (ResourceAllocationException e) {
|
||||
throw new CloudRuntimeException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
73
plugins/kms/database/pom.xml
Normal file
73
plugins/kms/database/pom.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloud-plugin-kms-database</artifactId>
|
||||
<name>Apache CloudStack Plugin - KMS Database Provider</name>
|
||||
<description>Database-backed KMS provider for encrypted key storage</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-kms-plugins</artifactId>
|
||||
<version>4.23.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-kms</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-config</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-utils</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.crypto.tink</groupId>
|
||||
<artifactId>tink</artifactId>
|
||||
<version>${cs.tink.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,386 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.provider;
|
||||
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.crypt.DBEncryptionUtil;
|
||||
import com.google.crypto.tink.subtle.AesGcmJce;
|
||||
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.framework.kms.KMSProvider;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
import org.apache.cloudstack.framework.kms.WrappedKey;
|
||||
import org.apache.cloudstack.kms.HSMProfileVO;
|
||||
import org.apache.cloudstack.kms.dao.HSMProfileDao;
|
||||
import org.apache.cloudstack.kms.provider.database.KMSDatabaseKekObjectVO;
|
||||
import org.apache.cloudstack.kms.provider.database.dao.KMSDatabaseKekObjectDao;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Database-backed KMS provider that stores master KEKs in a PKCS#11-like object table.
|
||||
* Uses AES-256-GCM for all cryptographic operations.
|
||||
* <p>
|
||||
* This provider is suitable for deployments that don't have access to HSM hardware.
|
||||
* The master KEKs are stored encrypted in the kms_database_kek_objects table using
|
||||
* CloudStack's existing DBEncryptionUtil, with PKCS#11-compatible attributes.
|
||||
*/
|
||||
public class DatabaseKMSProvider extends AdapterBase implements KMSProvider {
|
||||
private static final Logger logger = LogManager.getLogger(DatabaseKMSProvider.class);
|
||||
private static final String PROVIDER_NAME = "database";
|
||||
private static final int GCM_IV_LENGTH = 12; // 96 bits recommended for GCM
|
||||
private static final int GCM_TAG_LENGTH = 16; // 128 bits
|
||||
private static final String ALGORITHM = "AES/GCM/NoPadding";
|
||||
private static final String CKO_SECRET_KEY = "CKO_SECRET_KEY";
|
||||
private static final String CKK_AES = "CKK_AES";
|
||||
|
||||
private static final String DEFAULT_PROFILE_NAME = "HSM Database Provider";
|
||||
private static final long SYSTEM_ACCOUNT_ID = 1L;
|
||||
private static final long ROOT_DOMAIN_ID = 1L;
|
||||
|
||||
private final SecureRandom secureRandom = new SecureRandom();
|
||||
@Inject
|
||||
private KMSDatabaseKekObjectDao kekObjectDao;
|
||||
@Inject
|
||||
private HSMProfileDao hsmProfileDao;
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
super.start();
|
||||
ensureDefaultHSMProfile();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderName() {
|
||||
return PROVIDER_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmProfileId) throws KMSException {
|
||||
// Database provider ignores hsmProfileId
|
||||
return createKek(purpose, label, keyBits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createKek(KeyPurpose purpose, String label, int keyBits) throws KMSException {
|
||||
if (keyBits != 128 && keyBits != 192 && keyBits != 256) {
|
||||
throw KMSException.invalidParameter("Key size must be 128, 192, or 256 bits");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(label)) {
|
||||
throw KMSException.invalidParameter("KEK label cannot be empty");
|
||||
}
|
||||
|
||||
if (kekObjectDao.existsByLabel(label)) {
|
||||
throw KMSException.keyAlreadyExists("KEK with label " + label + " already exists");
|
||||
}
|
||||
|
||||
byte[] kekBytes = new byte[keyBits / 8];
|
||||
try {
|
||||
secureRandom.nextBytes(kekBytes);
|
||||
|
||||
// Base64 encode then encrypt the KEK material using DBEncryptionUtil
|
||||
String kekBase64 = Base64.getEncoder().encodeToString(kekBytes);
|
||||
String encryptedKek = DBEncryptionUtil.encrypt(kekBase64);
|
||||
byte[] encryptedKekBytes = encryptedKek.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
KMSDatabaseKekObjectVO kekObject = new KMSDatabaseKekObjectVO(label, purpose, keyBits, encryptedKekBytes);
|
||||
kekObject.setObjectClass(CKO_SECRET_KEY);
|
||||
kekObject.setKeyType(CKK_AES);
|
||||
kekObject.setObjectId(label.getBytes(StandardCharsets.UTF_8));
|
||||
kekObject.setAlgorithm(ALGORITHM);
|
||||
kekObject.setIsSensitive(true);
|
||||
kekObject.setIsExtractable(false);
|
||||
kekObject.setIsToken(true);
|
||||
kekObject.setIsPrivate(true);
|
||||
kekObject.setIsModifiable(false);
|
||||
kekObject.setIsCopyable(false);
|
||||
kekObject.setIsDestroyable(true);
|
||||
kekObject.setAlwaysSensitive(true);
|
||||
kekObject.setNeverExtractable(true);
|
||||
|
||||
kekObjectDao.persist(kekObject);
|
||||
|
||||
logger.info("Created KEK with label {} for purpose {} (PKCS#11 object ID: {})", label, purpose,
|
||||
kekObject.getId());
|
||||
return label;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw KMSException.kekOperationFailed("Failed to create KEK: " + e.getMessage(), e);
|
||||
} finally {
|
||||
Arrays.fill(kekBytes, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteKek(String kekId) throws KMSException {
|
||||
KMSDatabaseKekObjectVO kekObject = kekObjectDao.findByLabel(kekId);
|
||||
if (kekObject == null) {
|
||||
throw KMSException.kekNotFound("KEK with label " + kekId + " not found");
|
||||
}
|
||||
|
||||
try {
|
||||
kekObjectDao.remove(kekObject.getId());
|
||||
|
||||
if (kekObject.getKeyMaterial() != null) {
|
||||
Arrays.fill(kekObject.getKeyMaterial(), (byte) 0);
|
||||
}
|
||||
|
||||
logger.warn("Deleted KEK with label {}. All DEKs wrapped with this KEK are now unrecoverable!", kekId);
|
||||
} catch (Exception e) {
|
||||
throw KMSException.kekOperationFailed("Failed to delete KEK: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKekAvailable(String kekId) throws KMSException {
|
||||
try {
|
||||
KMSDatabaseKekObjectVO kekObject = kekObjectDao.findByLabel(kekId);
|
||||
return kekObject != null && kekObject.getRemoved() == null && kekObject.getKeyMaterial() != null;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error checking KEK availability: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey wrapKey(byte[] plainKey, KeyPurpose purpose, String kekLabel,
|
||||
Long hsmProfileId) throws KMSException {
|
||||
// Database provider ignores hsmProfileId
|
||||
return wrapKey(plainKey, purpose, kekLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey wrapKey(byte[] plainKey, KeyPurpose purpose, String kekLabel) throws KMSException {
|
||||
if (plainKey == null || plainKey.length == 0) {
|
||||
throw KMSException.invalidParameter("Plain key cannot be null or empty");
|
||||
}
|
||||
|
||||
byte[] kekBytes = loadKek(kekLabel);
|
||||
|
||||
try {
|
||||
// Tink's AesGcmJce automatically generates a random IV and prepends it to the ciphertext
|
||||
AesGcmJce aesgcm = new AesGcmJce(kekBytes);
|
||||
byte[] wrappedBlob = aesgcm.encrypt(plainKey, new byte[0]);
|
||||
|
||||
WrappedKey wrapped = new WrappedKey(kekLabel, purpose, ALGORITHM, wrappedBlob, PROVIDER_NAME, new Date(),
|
||||
null);
|
||||
|
||||
logger.debug("Wrapped {} key with KEK {}", purpose, kekLabel);
|
||||
return wrapped;
|
||||
} catch (Exception e) {
|
||||
throw KMSException.wrapUnwrapFailed("Failed to wrap key: " + e.getMessage(), e);
|
||||
} finally {
|
||||
// Zeroize KEK
|
||||
Arrays.fill(kekBytes, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSException {
|
||||
// Database provider ignores hsmProfileId
|
||||
return unwrapKey(wrappedKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] unwrapKey(WrappedKey wrappedKey) throws KMSException {
|
||||
if (wrappedKey == null) {
|
||||
throw KMSException.invalidParameter("Wrapped key cannot be null");
|
||||
}
|
||||
|
||||
byte[] kekBytes = loadKek(wrappedKey.getKekId());
|
||||
|
||||
try {
|
||||
AesGcmJce aesgcm = new AesGcmJce(kekBytes);
|
||||
// Tink's decrypt expects [IV][ciphertext+tag] format (same as encrypt returns)
|
||||
byte[] blob = wrappedKey.getWrappedKeyMaterial();
|
||||
if (blob.length < GCM_IV_LENGTH + GCM_TAG_LENGTH) {
|
||||
throw new KMSException(KMSException.ErrorType.WRAP_UNWRAP_FAILED,
|
||||
"Invalid wrapped key format: too short");
|
||||
}
|
||||
|
||||
byte[] plainKey = aesgcm.decrypt(blob, new byte[0]);
|
||||
|
||||
logger.debug("Unwrapped {} key with KEK {}", wrappedKey.getPurpose(), wrappedKey.getKekId());
|
||||
return plainKey;
|
||||
|
||||
} catch (KMSException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw KMSException.wrapUnwrapFailed("Failed to unwrap key: " + e.getMessage(), e);
|
||||
} finally {
|
||||
// Zeroize KEK
|
||||
Arrays.fill(kekBytes, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits,
|
||||
Long hsmProfileId) throws KMSException {
|
||||
// Database provider ignores hsmProfileId
|
||||
return generateAndWrapDek(purpose, kekLabel, keyBits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits) throws KMSException {
|
||||
if (keyBits != 128 && keyBits != 192 && keyBits != 256) {
|
||||
throw KMSException.invalidParameter("DEK size must be 128, 192, or 256 bits");
|
||||
}
|
||||
|
||||
byte[] dekBytes = new byte[keyBits / 8];
|
||||
secureRandom.nextBytes(dekBytes);
|
||||
|
||||
try {
|
||||
return wrapKey(dekBytes, purpose, kekLabel);
|
||||
} finally {
|
||||
// Zeroize DEK (wrapped version is in WrappedKey)
|
||||
Arrays.fill(dekBytes, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel,
|
||||
Long targetHsmProfileId) throws KMSException {
|
||||
// Database provider ignores targetHsmProfileId
|
||||
return rewrapKey(oldWrappedKey, newKekLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel) throws KMSException {
|
||||
byte[] plainKey = unwrapKey(oldWrappedKey);
|
||||
try {
|
||||
return wrapKey(plainKey, oldWrappedKey.getPurpose(), newKekLabel);
|
||||
} finally {
|
||||
// Zeroize plaintext DEK
|
||||
Arrays.fill(plainKey, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean healthCheck() throws KMSException {
|
||||
try {
|
||||
if (kekObjectDao == null) {
|
||||
logger.error("KMSDatabaseKekObjectDao is not initialized");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw KMSException.healthCheckFailed("Health check failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] loadKek(String kekLabel) throws KMSException {
|
||||
KMSDatabaseKekObjectVO kekObject = kekObjectDao.findByLabel(kekLabel);
|
||||
|
||||
if (kekObject == null || kekObject.getRemoved() != null) {
|
||||
throw KMSException.kekNotFound("KEK with label " + kekLabel + " not found");
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] encryptedKekBytes = kekObject.getKeyMaterial();
|
||||
if (encryptedKekBytes == null || encryptedKekBytes.length == 0) {
|
||||
throw KMSException.kekNotFound("KEK value is empty for label " + kekLabel);
|
||||
}
|
||||
|
||||
String encryptedKek = new String(encryptedKekBytes, StandardCharsets.UTF_8);
|
||||
String kekBase64 = DBEncryptionUtil.decrypt(encryptedKek);
|
||||
byte[] kekBytes = Base64.getDecoder().decode(kekBase64);
|
||||
|
||||
updateLastUsed(kekLabel);
|
||||
|
||||
return kekBytes;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw KMSException.kekOperationFailed("Invalid KEK encoding for label " + kekLabel, e);
|
||||
} catch (Exception e) {
|
||||
throw KMSException.kekOperationFailed("Failed to decrypt KEK for label " + kekLabel + ": " + e.getMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLastUsed(String kekLabel) {
|
||||
try {
|
||||
KMSDatabaseKekObjectVO kekObject = kekObjectDao.findByLabel(kekLabel);
|
||||
if (kekObject != null && kekObject.getRemoved() == null) {
|
||||
kekObject.setLastUsed(new Date());
|
||||
kekObjectDao.update(kekObject.getId(), kekObject);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to update last used timestamp for KEK {}: {}", kekLabel, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeds the default database HSM profile if it does not already exist.
|
||||
* This runs at provider startup to avoid FK constraint issues that occur
|
||||
* when the INSERT is placed in the schema upgrade SQL script (the account
|
||||
* table may not yet be populated when the upgrade script executes on a
|
||||
* fresh install).
|
||||
*/
|
||||
private void ensureDefaultHSMProfile() {
|
||||
try {
|
||||
SearchBuilder<HSMProfileVO> sb = hsmProfileDao.createSearchBuilder();
|
||||
sb.and("protocol", sb.entity().getProtocol(), SearchCriteria.Op.EQ);
|
||||
sb.done();
|
||||
|
||||
SearchCriteria<HSMProfileVO> sc = sb.create();
|
||||
sc.setParameters("protocol", PROVIDER_NAME);
|
||||
|
||||
List<HSMProfileVO> existing = hsmProfileDao.customSearchIncludingRemoved(sc, null);
|
||||
if (existing != null && !existing.isEmpty()) {
|
||||
logger.debug("Default database HSM profile already exists (id={})", existing.get(0).getId());
|
||||
return;
|
||||
}
|
||||
|
||||
HSMProfileVO profile = new HSMProfileVO(DEFAULT_PROFILE_NAME, PROVIDER_NAME,
|
||||
SYSTEM_ACCOUNT_ID, ROOT_DOMAIN_ID, null, null);
|
||||
profile.setEnabled(false);
|
||||
profile.setIsPublic(true);
|
||||
hsmProfileDao.persist(profile);
|
||||
logger.info("Seeded default database HSM profile (id={}, uuid={})", profile.getId(), profile.getUuid());
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to seed default database HSM profile: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getConfigComponentName() {
|
||||
return DatabaseKMSProvider.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[0];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,357 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.provider.database;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Database entity for KEK objects stored by the database KMS provider.
|
||||
* Models PKCS#11 object attributes for cryptographic key storage.
|
||||
* <p>
|
||||
* This table stores KEKs (Key Encryption Keys) in a PKCS#11-compatible format,
|
||||
* allowing the database provider to mock PKCS#11 interface behavior.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "kms_database_kek_objects")
|
||||
public class KMSDatabaseKekObjectVO {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "uuid", nullable = false)
|
||||
private String uuid;
|
||||
|
||||
// PKCS#11 Object Class (CKA_CLASS)
|
||||
@Column(name = "object_class", nullable = false, length = 32)
|
||||
private String objectClass = "CKO_SECRET_KEY";
|
||||
|
||||
// PKCS#11 Label (CKA_LABEL) - human-readable identifier
|
||||
@Column(name = "label", nullable = false, length = 255)
|
||||
private String label;
|
||||
|
||||
// PKCS#11 ID (CKA_ID) - application-defined identifier
|
||||
@Column(name = "object_id", length = 64)
|
||||
private byte[] objectId;
|
||||
|
||||
// PKCS#11 Key Type (CKA_KEY_TYPE)
|
||||
@Column(name = "key_type", nullable = false, length = 32)
|
||||
private String keyType = "CKK_AES";
|
||||
|
||||
// PKCS#11 Key Value (CKA_VALUE) - encrypted KEK material
|
||||
@Column(name = "key_material", nullable = false, length = 512)
|
||||
private byte[] keyMaterial;
|
||||
|
||||
// PKCS#11 Boolean Attributes
|
||||
@Column(name = "is_sensitive", nullable = false)
|
||||
private Boolean isSensitive = true;
|
||||
|
||||
@Column(name = "is_extractable", nullable = false)
|
||||
private Boolean isExtractable = false;
|
||||
|
||||
@Column(name = "is_token", nullable = false)
|
||||
private Boolean isToken = true;
|
||||
|
||||
@Column(name = "is_private", nullable = false)
|
||||
private Boolean isPrivate = true;
|
||||
|
||||
@Column(name = "is_modifiable", nullable = false)
|
||||
private Boolean isModifiable = false;
|
||||
|
||||
@Column(name = "is_copyable", nullable = false)
|
||||
private Boolean isCopyable = false;
|
||||
|
||||
@Column(name = "is_destroyable", nullable = false)
|
||||
private Boolean isDestroyable = true;
|
||||
|
||||
@Column(name = "always_sensitive", nullable = false)
|
||||
private Boolean alwaysSensitive = true;
|
||||
|
||||
@Column(name = "never_extractable", nullable = false)
|
||||
private Boolean neverExtractable = true;
|
||||
|
||||
// Key Metadata
|
||||
@Column(name = "purpose", nullable = false, length = 32)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private KeyPurpose purpose;
|
||||
|
||||
@Column(name = "key_bits", nullable = false)
|
||||
private Integer keyBits;
|
||||
|
||||
@Column(name = "algorithm", nullable = false, length = 64)
|
||||
private String algorithm = "AES/GCM/NoPadding";
|
||||
|
||||
// PKCS#11 Validity Dates
|
||||
@Column(name = "start_date")
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date startDate;
|
||||
|
||||
@Column(name = "end_date")
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date endDate;
|
||||
|
||||
// Lifecycle
|
||||
@Column(name = GenericDao.CREATED_COLUMN, nullable = false)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
||||
@Column(name = "last_used")
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date lastUsed;
|
||||
|
||||
@Column(name = GenericDao.REMOVED_COLUMN)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date removed;
|
||||
|
||||
/**
|
||||
* Constructor for creating a new KEK object
|
||||
*
|
||||
* @param label PKCS#11 label (CKA_LABEL)
|
||||
* @param purpose key purpose
|
||||
* @param keyBits key size in bits
|
||||
* @param keyMaterial encrypted key material (CKA_VALUE)
|
||||
*/
|
||||
public KMSDatabaseKekObjectVO(String label, KeyPurpose purpose, Integer keyBits, byte[] keyMaterial) {
|
||||
this();
|
||||
this.label = label;
|
||||
this.purpose = purpose;
|
||||
this.keyBits = keyBits;
|
||||
this.keyMaterial = keyMaterial;
|
||||
this.objectId = label.getBytes(); // Use label as object ID by default
|
||||
this.startDate = new Date();
|
||||
}
|
||||
|
||||
public KMSDatabaseKekObjectVO() {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.created = new Date();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getObjectClass() {
|
||||
return objectClass;
|
||||
}
|
||||
|
||||
public void setObjectClass(String objectClass) {
|
||||
this.objectClass = objectClass;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public byte[] getObjectId() {
|
||||
return objectId;
|
||||
}
|
||||
|
||||
public void setObjectId(byte[] objectId) {
|
||||
this.objectId = objectId;
|
||||
}
|
||||
|
||||
public String getKeyType() {
|
||||
return keyType;
|
||||
}
|
||||
|
||||
public void setKeyType(String keyType) {
|
||||
this.keyType = keyType;
|
||||
}
|
||||
|
||||
public byte[] getKeyMaterial() {
|
||||
return keyMaterial;
|
||||
}
|
||||
|
||||
public void setKeyMaterial(byte[] keyMaterial) {
|
||||
this.keyMaterial = keyMaterial;
|
||||
}
|
||||
|
||||
public Boolean getIsSensitive() {
|
||||
return isSensitive;
|
||||
}
|
||||
|
||||
public void setIsSensitive(Boolean isSensitive) {
|
||||
this.isSensitive = isSensitive;
|
||||
}
|
||||
|
||||
public Boolean getIsExtractable() {
|
||||
return isExtractable;
|
||||
}
|
||||
|
||||
public void setIsExtractable(Boolean isExtractable) {
|
||||
this.isExtractable = isExtractable;
|
||||
}
|
||||
|
||||
public Boolean getIsToken() {
|
||||
return isToken;
|
||||
}
|
||||
|
||||
public void setIsToken(Boolean isToken) {
|
||||
this.isToken = isToken;
|
||||
}
|
||||
|
||||
public Boolean getIsPrivate() {
|
||||
return isPrivate;
|
||||
}
|
||||
|
||||
public void setIsPrivate(Boolean isPrivate) {
|
||||
this.isPrivate = isPrivate;
|
||||
}
|
||||
|
||||
public Boolean getIsModifiable() {
|
||||
return isModifiable;
|
||||
}
|
||||
|
||||
public void setIsModifiable(Boolean isModifiable) {
|
||||
this.isModifiable = isModifiable;
|
||||
}
|
||||
|
||||
public Boolean getIsCopyable() {
|
||||
return isCopyable;
|
||||
}
|
||||
|
||||
public void setIsCopyable(Boolean isCopyable) {
|
||||
this.isCopyable = isCopyable;
|
||||
}
|
||||
|
||||
public Boolean getIsDestroyable() {
|
||||
return isDestroyable;
|
||||
}
|
||||
|
||||
public void setIsDestroyable(Boolean isDestroyable) {
|
||||
this.isDestroyable = isDestroyable;
|
||||
}
|
||||
|
||||
public Boolean getAlwaysSensitive() {
|
||||
return alwaysSensitive;
|
||||
}
|
||||
|
||||
public void setAlwaysSensitive(Boolean alwaysSensitive) {
|
||||
this.alwaysSensitive = alwaysSensitive;
|
||||
}
|
||||
|
||||
public Boolean getNeverExtractable() {
|
||||
return neverExtractable;
|
||||
}
|
||||
|
||||
public void setNeverExtractable(Boolean neverExtractable) {
|
||||
this.neverExtractable = neverExtractable;
|
||||
}
|
||||
|
||||
public KeyPurpose getPurpose() {
|
||||
return purpose;
|
||||
}
|
||||
|
||||
public void setPurpose(KeyPurpose purpose) {
|
||||
this.purpose = purpose;
|
||||
}
|
||||
|
||||
public Integer getKeyBits() {
|
||||
return keyBits;
|
||||
}
|
||||
|
||||
public void setKeyBits(Integer keyBits) {
|
||||
this.keyBits = keyBits;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public Date getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setStartDate(Date startDate) {
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
public Date getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public void setEndDate(Date endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public Date getLastUsed() {
|
||||
return lastUsed;
|
||||
}
|
||||
|
||||
public void setLastUsed(Date lastUsed) {
|
||||
this.lastUsed = lastUsed;
|
||||
}
|
||||
|
||||
public Date getRemoved() {
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void setRemoved(Date removed) {
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("KMSDatabaseKekObject %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "label", "purpose", "keyBits", "objectClass", "keyType", "algorithm"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.provider.database.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
import org.apache.cloudstack.kms.provider.database.KMSDatabaseKekObjectVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DAO for KMSDatabaseKekObject entities
|
||||
* Provides PKCS#11-like object storage operations for KEKs
|
||||
*/
|
||||
public interface KMSDatabaseKekObjectDao extends GenericDao<KMSDatabaseKekObjectVO, Long> {
|
||||
|
||||
/**
|
||||
* Find a KEK object by label (PKCS#11 CKA_LABEL)
|
||||
*/
|
||||
KMSDatabaseKekObjectVO findByLabel(String label);
|
||||
|
||||
/**
|
||||
* Find a KEK object by object ID (PKCS#11 CKA_ID)
|
||||
*/
|
||||
KMSDatabaseKekObjectVO findByObjectId(byte[] objectId);
|
||||
|
||||
/**
|
||||
* List all KEK objects by purpose
|
||||
*/
|
||||
List<KMSDatabaseKekObjectVO> listByPurpose(KeyPurpose purpose);
|
||||
|
||||
/**
|
||||
* List all KEK objects by key type (PKCS#11 CKA_KEY_TYPE)
|
||||
*/
|
||||
List<KMSDatabaseKekObjectVO> listByKeyType(String keyType);
|
||||
|
||||
/**
|
||||
* List all KEK objects by object class (PKCS#11 CKA_CLASS)
|
||||
*/
|
||||
List<KMSDatabaseKekObjectVO> listByObjectClass(String objectClass);
|
||||
|
||||
/**
|
||||
* Check if a KEK object exists with the given label
|
||||
*/
|
||||
boolean existsByLabel(String label);
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.provider.database.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
import org.apache.cloudstack.kms.provider.database.KMSDatabaseKekObjectVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class KMSDatabaseKekObjectDaoImpl extends GenericDaoBase<KMSDatabaseKekObjectVO, Long> implements KMSDatabaseKekObjectDao {
|
||||
|
||||
private final SearchBuilder<KMSDatabaseKekObjectVO> allFieldSearch;
|
||||
|
||||
public KMSDatabaseKekObjectDaoImpl() {
|
||||
allFieldSearch = createSearchBuilder();
|
||||
allFieldSearch.and("uuid", allFieldSearch.entity().getUuid(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("label", allFieldSearch.entity().getLabel(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("objectId", allFieldSearch.entity().getObjectId(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("purpose", allFieldSearch.entity().getPurpose(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("keyType", allFieldSearch.entity().getKeyType(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.and("objectClass", allFieldSearch.entity().getObjectClass(), SearchCriteria.Op.EQ);
|
||||
allFieldSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KMSDatabaseKekObjectVO findByLabel(String label) {
|
||||
SearchCriteria<KMSDatabaseKekObjectVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("label", label);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KMSDatabaseKekObjectVO findByObjectId(byte[] objectId) {
|
||||
SearchCriteria<KMSDatabaseKekObjectVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("objectId", objectId);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSDatabaseKekObjectVO> listByPurpose(KeyPurpose purpose) {
|
||||
SearchCriteria<KMSDatabaseKekObjectVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("purpose", purpose);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSDatabaseKekObjectVO> listByKeyType(String keyType) {
|
||||
SearchCriteria<KMSDatabaseKekObjectVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("keyType", keyType);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KMSDatabaseKekObjectVO> listByObjectClass(String objectClass) {
|
||||
SearchCriteria<KMSDatabaseKekObjectVO> sc = allFieldSearch.create();
|
||||
sc.setParameters("objectClass", objectClass);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByLabel(String label) {
|
||||
return findByLabel(label) != null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
# 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.
|
||||
name=database-kms
|
||||
parent=kms
|
||||
@ -0,0 +1,31 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<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.database"/>
|
||||
<bean id="databaseKMSProvider" class="org.apache.cloudstack.kms.provider.DatabaseKMSProvider">
|
||||
<property name="name" value="DatabaseKMSProvider"/>
|
||||
</bean>
|
||||
</beans>
|
||||
73
plugins/kms/pkcs11/pom.xml
Normal file
73
plugins/kms/pkcs11/pom.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloud-plugin-kms-pkcs11</artifactId>
|
||||
<name>Apache CloudStack Plugin - KMS PKCS#11 Provider</name>
|
||||
<description>PKCS#11-backed KMS provider for HSM integration</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-kms-plugins</artifactId>
|
||||
<version>4.23.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-kms</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-config</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-utils</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-engine-schema</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,21 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
name=pkcs11-kms
|
||||
parent=kms
|
||||
@ -0,0 +1,32 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<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"/>
|
||||
<bean id="pkcs11HSMProvider" class="org.apache.cloudstack.kms.provider.pkcs11.PKCS11HSMProvider">
|
||||
<property name="name" value="PKCS11HSMProvider"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
@ -0,0 +1,294 @@
|
||||
// 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.
|
||||
|
||||
package org.apache.cloudstack.kms.provider.pkcs11;
|
||||
|
||||
import org.apache.cloudstack.framework.kms.KMSException;
|
||||
import org.apache.cloudstack.framework.kms.KeyPurpose;
|
||||
import org.apache.cloudstack.kms.HSMProfileDetailsVO;
|
||||
import org.apache.cloudstack.kms.KMSKekVersionVO;
|
||||
import org.apache.cloudstack.kms.dao.HSMProfileDetailsDao;
|
||||
import org.apache.cloudstack.kms.dao.KMSKekVersionDao;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for PKCS11HSMProvider
|
||||
* Tests provider-specific logic: config loading, profile resolution, sensitive key detection
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class PKCS11HSMProviderTest {
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
private PKCS11HSMProvider provider;
|
||||
|
||||
@Mock
|
||||
private HSMProfileDetailsDao hsmProfileDetailsDao;
|
||||
|
||||
@Mock
|
||||
private KMSKekVersionDao kmsKekVersionDao;
|
||||
|
||||
private Long testProfileId = 1L;
|
||||
private String testKekLabel = "test-kek-label";
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Minimal setup
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: resolveProfileId successfully finds profile from KEK label
|
||||
*/
|
||||
@Test
|
||||
public void testResolveProfileId_FindsFromKekLabel() throws KMSException {
|
||||
// Setup: KEK version with profile ID
|
||||
KMSKekVersionVO kekVersion = mock(KMSKekVersionVO.class);
|
||||
when(kekVersion.getHsmProfileId()).thenReturn(testProfileId);
|
||||
when(kmsKekVersionDao.findByKekLabel(testKekLabel)).thenReturn(kekVersion);
|
||||
|
||||
// Test
|
||||
Long result = provider.resolveProfileId(testKekLabel);
|
||||
|
||||
// Verify
|
||||
assertNotNull("Should return profile ID", result);
|
||||
assertEquals("Should return correct profile ID", testProfileId, result);
|
||||
verify(kmsKekVersionDao).findByKekLabel(testKekLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: resolveProfileId throws exception when KEK version not found
|
||||
*/
|
||||
@Test(expected = KMSException.class)
|
||||
public void testResolveProfileId_ThrowsExceptionWhenVersionNotFound() throws KMSException {
|
||||
// Setup: No KEK version found
|
||||
when(kmsKekVersionDao.findByKekLabel(testKekLabel)).thenReturn(null);
|
||||
|
||||
// Test - should throw exception
|
||||
provider.resolveProfileId(testKekLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: resolveProfileId throws exception when profile ID is null
|
||||
*/
|
||||
@Test(expected = KMSException.class)
|
||||
public void testResolveProfileId_ThrowsExceptionWhenProfileIdNull() throws KMSException {
|
||||
// Setup: KEK version exists but has null profile ID
|
||||
KMSKekVersionVO kekVersion = mock(KMSKekVersionVO.class);
|
||||
when(kekVersion.getHsmProfileId()).thenReturn(null);
|
||||
when(kmsKekVersionDao.findByKekLabel(testKekLabel)).thenReturn(kekVersion);
|
||||
|
||||
// Test - should throw exception
|
||||
provider.resolveProfileId(testKekLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: loadProfileConfig loads and decrypts sensitive values
|
||||
*/
|
||||
@Test
|
||||
public void testLoadProfileConfig_DecryptsSensitiveValues() {
|
||||
// Setup: Profile details with encrypted pin
|
||||
HSMProfileDetailsVO detail1 = mock(HSMProfileDetailsVO.class);
|
||||
when(detail1.getName()).thenReturn("library");
|
||||
when(detail1.getValue()).thenReturn("/path/to/lib.so");
|
||||
|
||||
HSMProfileDetailsVO detail2 = mock(HSMProfileDetailsVO.class);
|
||||
when(detail2.getName()).thenReturn("pin");
|
||||
when(detail2.getValue()).thenReturn("ENC(encrypted_pin)");
|
||||
|
||||
HSMProfileDetailsVO detail3 = mock(HSMProfileDetailsVO.class);
|
||||
when(detail3.getName()).thenReturn("slot");
|
||||
when(detail3.getValue()).thenReturn("0");
|
||||
|
||||
when(hsmProfileDetailsDao.listByProfileId(testProfileId)).thenReturn(
|
||||
Arrays.asList(detail1, detail2, detail3));
|
||||
|
||||
// Test
|
||||
Map<String, String> config = provider.loadProfileConfig(testProfileId);
|
||||
|
||||
// Verify
|
||||
assertNotNull("Config should not be null", config);
|
||||
assertEquals(3, config.size());
|
||||
assertEquals("/path/to/lib.so", config.get("library"));
|
||||
// Note: In real code, DBEncryptionUtil.decrypt would be called
|
||||
// Here we just verify the structure is correct
|
||||
assertTrue("Config should contain pin", config.containsKey("pin"));
|
||||
assertEquals("0", config.get("slot"));
|
||||
|
||||
verify(hsmProfileDetailsDao).listByProfileId(testProfileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: loadProfileConfig handles empty details
|
||||
*/
|
||||
@Test(expected = KMSException.class)
|
||||
public void testLoadProfileConfig_HandlesEmptyDetails() {
|
||||
// Setup
|
||||
when(hsmProfileDetailsDao.listByProfileId(testProfileId)).thenReturn(Arrays.asList());
|
||||
|
||||
// Test
|
||||
Map<String, String> config = provider.loadProfileConfig(testProfileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: isSensitiveKey correctly identifies sensitive keys
|
||||
*/
|
||||
@Test
|
||||
public void testIsSensitiveKey_IdentifiesSensitiveKeys() {
|
||||
// Test
|
||||
assertTrue(provider.isSensitiveKey("pin"));
|
||||
assertTrue(provider.isSensitiveKey("password"));
|
||||
assertTrue(provider.isSensitiveKey("api_secret"));
|
||||
assertTrue(provider.isSensitiveKey("private_key"));
|
||||
assertTrue(provider.isSensitiveKey("PIN")); // Case-insensitive
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: isSensitiveKey correctly identifies non-sensitive keys
|
||||
*/
|
||||
@Test
|
||||
public void testIsSensitiveKey_IdentifiesNonSensitiveKeys() {
|
||||
// Test
|
||||
assertFalse(provider.isSensitiveKey("library"));
|
||||
assertFalse(provider.isSensitiveKey("slot_id"));
|
||||
assertFalse(provider.isSensitiveKey("endpoint"));
|
||||
assertFalse(provider.isSensitiveKey("max_sessions"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: getProviderName returns correct name
|
||||
*/
|
||||
@Test
|
||||
public void testGetProviderName() {
|
||||
assertEquals("pkcs11", provider.getProviderName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: createKek requires hsmProfileId
|
||||
*/
|
||||
@Test(expected = KMSException.class)
|
||||
public void testCreateKek_RequiresProfileId() throws KMSException {
|
||||
provider.createKek(
|
||||
KeyPurpose.VOLUME_ENCRYPTION,
|
||||
"test-label",
|
||||
256,
|
||||
null // null profile ID should throw exception
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: getSessionPool creates pool for new profile
|
||||
*/
|
||||
@Test
|
||||
public void testGetSessionPool_CreatesPoolForNewProfile() {
|
||||
// Setup
|
||||
HSMProfileDetailsVO libraryDetail = mock(HSMProfileDetailsVO.class);
|
||||
when(libraryDetail.getName()).thenReturn("library");
|
||||
when(libraryDetail.getValue()).thenReturn("/path/to/lib.so");
|
||||
HSMProfileDetailsVO slotDetail = mock(HSMProfileDetailsVO.class);
|
||||
when(slotDetail.getName()).thenReturn("slot");
|
||||
when(slotDetail.getValue()).thenReturn("1");
|
||||
HSMProfileDetailsVO pinDetail = mock(HSMProfileDetailsVO.class);
|
||||
when(pinDetail.getName()).thenReturn("pin");
|
||||
when(pinDetail.getValue()).thenReturn("1234");
|
||||
when(hsmProfileDetailsDao.listByProfileId(testProfileId)).thenReturn(
|
||||
Arrays.asList(libraryDetail, slotDetail, pinDetail));
|
||||
|
||||
// Test
|
||||
Object pool = provider.getSessionPool(testProfileId);
|
||||
|
||||
// Verify
|
||||
assertNotNull("Pool should be created", pool);
|
||||
verify(hsmProfileDetailsDao).listByProfileId(testProfileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: getSessionPool reuses pool for same profile
|
||||
*/
|
||||
@Test
|
||||
public void testGetSessionPool_ReusesPoolForSameProfile() {
|
||||
// Setup
|
||||
HSMProfileDetailsVO libraryDetail = mock(HSMProfileDetailsVO.class);
|
||||
when(libraryDetail.getName()).thenReturn("library");
|
||||
when(libraryDetail.getValue()).thenReturn("/path/to/lib.so");
|
||||
HSMProfileDetailsVO slotDetail = mock(HSMProfileDetailsVO.class);
|
||||
when(slotDetail.getName()).thenReturn("slot");
|
||||
when(slotDetail.getValue()).thenReturn("1");
|
||||
HSMProfileDetailsVO pinDetail = mock(HSMProfileDetailsVO.class);
|
||||
when(pinDetail.getName()).thenReturn("pin");
|
||||
when(pinDetail.getValue()).thenReturn("1234");
|
||||
when(hsmProfileDetailsDao.listByProfileId(testProfileId)).thenReturn(
|
||||
Arrays.asList(libraryDetail, slotDetail, pinDetail));
|
||||
|
||||
// Test
|
||||
Object pool1 = provider.getSessionPool(testProfileId);
|
||||
Object pool2 = provider.getSessionPool(testProfileId);
|
||||
|
||||
// Verify
|
||||
assertNotNull("Pool should be created", pool1);
|
||||
assertEquals("Should reuse same pool", pool1, pool2);
|
||||
// Config should only be loaded once
|
||||
verify(hsmProfileDetailsDao, times(1)).listByProfileId(testProfileId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateProfileCache_ForcesFreshPool() {
|
||||
// Setup
|
||||
HSMProfileDetailsVO libraryDetail = mock(HSMProfileDetailsVO.class);
|
||||
when(libraryDetail.getName()).thenReturn("library");
|
||||
when(libraryDetail.getValue()).thenReturn("/path/to/lib.so");
|
||||
HSMProfileDetailsVO slotDetail = mock(HSMProfileDetailsVO.class);
|
||||
when(slotDetail.getName()).thenReturn("slot");
|
||||
when(slotDetail.getValue()).thenReturn("1");
|
||||
HSMProfileDetailsVO pinDetail = mock(HSMProfileDetailsVO.class);
|
||||
when(pinDetail.getName()).thenReturn("pin");
|
||||
when(pinDetail.getValue()).thenReturn("1234");
|
||||
when(hsmProfileDetailsDao.listByProfileId(testProfileId)).thenReturn(
|
||||
Arrays.asList(libraryDetail, slotDetail, pinDetail));
|
||||
|
||||
// Test: build a pool, invalidate it, then build again
|
||||
Object poolBefore = provider.getSessionPool(testProfileId);
|
||||
provider.invalidateProfileCache(testProfileId);
|
||||
Object poolAfter = provider.getSessionPool(testProfileId);
|
||||
|
||||
// Verify: a brand-new pool instance is created after invalidation
|
||||
assertNotNull("Pool should be created before invalidation", poolBefore);
|
||||
assertNotNull("Pool should be recreated after invalidation", poolAfter);
|
||||
assertNotSame("Invalidation must force a fresh pool, not reuse the stale one",
|
||||
poolBefore, poolAfter);
|
||||
// Config is reloaded for each freshly-built pool (twice total)
|
||||
verify(hsmProfileDetailsDao, times(2)).listByProfileId(testProfileId);
|
||||
}
|
||||
}
|
||||
40
plugins/kms/pom.xml
Normal file
40
plugins/kms/pom.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloudstack-kms-plugins</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Apache CloudStack Plugin - KMS</name>
|
||||
<description>Key Management Service providers</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-plugins</artifactId>
|
||||
<version>4.23.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
<module>database</module>
|
||||
<module>pkcs11</module>
|
||||
</modules>
|
||||
</project>
|
||||
@ -96,6 +96,8 @@
|
||||
<module>integrations/kubernetes-service</module>
|
||||
<module>integrations/veeam-control-service</module>
|
||||
|
||||
<module>kms</module>
|
||||
|
||||
<module>metrics</module>
|
||||
|
||||
<module>network-elements/bigswitch</module>
|
||||
|
||||
@ -199,7 +199,7 @@ public class StorageVmSharedFSLifeCycle implements SharedFSLifeCycle {
|
||||
diskOfferingId, size, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData,
|
||||
null, null, keypairs, null, addrs, null, null, null,
|
||||
customParameterMap, null, null, null, null,
|
||||
true, UserVmManager.SHAREDFSVM, null, null, null);
|
||||
true, UserVmManager.SHAREDFSVM, null, null, null, null);
|
||||
vmContext.setEventResourceId(vm.getId());
|
||||
userVmService.startVirtualMachine(vm, null);
|
||||
} catch (InsufficientCapacityException ex) {
|
||||
|
||||
@ -257,7 +257,7 @@ public class StorageVmSharedFSLifeCycleTest {
|
||||
anyString(), anyLong(), anyLong(), any(), isNull(), any(Hypervisor.HypervisorType.class), any(BaseCmd.HTTPMethod.class), anyString(),
|
||||
isNull(), isNull(), anyList(), isNull(), any(Network.IpAddresses.class), isNull(), isNull(), isNull(),
|
||||
anyMap(), isNull(), isNull(), isNull(), isNull(),
|
||||
anyBoolean(), anyString(), isNull(), isNull(), isNull())).thenReturn(vm);
|
||||
anyBoolean(), anyString(), isNull(), isNull(), isNull(), isNull())).thenReturn(vm);
|
||||
|
||||
VolumeVO rootVol = mock(VolumeVO.class);
|
||||
when(rootVol.getVolumeType()).thenReturn(Volume.Type.ROOT);
|
||||
|
||||
@ -633,11 +633,25 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri
|
||||
*/
|
||||
private boolean anyVolumeRequiresEncryption(DataObject ... objects) {
|
||||
for (DataObject o : objects) {
|
||||
// this fails code smell for returning true twice, but it is more readable than combining all tests into one statement
|
||||
if (o instanceof VolumeInfo && ((VolumeInfo) o).getPassphraseId() != null) {
|
||||
return true;
|
||||
} else if (o instanceof SnapshotInfo && ((SnapshotInfo) o).getBaseVolume().getPassphraseId() != null) {
|
||||
return true;
|
||||
// Check for legacy passphrase-based encryption
|
||||
if (o instanceof VolumeInfo) {
|
||||
VolumeInfo vol = (VolumeInfo) o;
|
||||
if (vol.getPassphraseId() != null) {
|
||||
return true;
|
||||
}
|
||||
// Check for KMS-based encryption
|
||||
if (vol.getKmsWrappedKeyId() != null || vol.getKmsKeyId() != null) {
|
||||
return true;
|
||||
}
|
||||
} else if (o instanceof SnapshotInfo) {
|
||||
VolumeInfo baseVol = ((SnapshotInfo) o).getBaseVolume();
|
||||
if (baseVol.getPassphraseId() != null) {
|
||||
return true;
|
||||
}
|
||||
// Check for KMS-based encryption
|
||||
if (baseVol.getKmsWrappedKeyId() != null || baseVol.getKmsKeyId() != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -436,7 +436,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
|
||||
logger.info("Clone resource definition {} to {}", cloneRes, rscName);
|
||||
ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
|
||||
cloneRequest.setName(rscName);
|
||||
if (volumeInfo.getPassphraseId() != null) {
|
||||
if (volumeInfo.getPassphraseId() != null || volumeInfo.getKmsKeyId() != null) {
|
||||
List<LayerType> encryptionLayer = LinstorUtil.getEncryptedLayerList(
|
||||
linstorApi, LinstorUtil.getRscGrp(storagePoolVO));
|
||||
cloneRequest.setLayerList(encryptionLayer);
|
||||
|
||||
@ -399,7 +399,7 @@ public class LinstorDataMotionStrategy implements DataMotionStrategy {
|
||||
VolumeVO srcVolume = _volumeDao.findById(srcVolumeInfo.getId());
|
||||
StoragePoolVO destStoragePool = _storagePool.findById(destDataStore.getId());
|
||||
|
||||
if (srcVolumeInfo.getPassphraseId() != null) {
|
||||
if (srcVolumeInfo.getPassphraseId() != null || srcVolumeInfo.getKmsKeyId() != null) {
|
||||
throw new CloudRuntimeException(
|
||||
String.format("Cannot live migrate encrypted volume: %s", srcVolumeInfo.getVolume()));
|
||||
}
|
||||
|
||||
@ -1601,7 +1601,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
*/
|
||||
protected boolean anyVolumeRequiresEncryption(DataObject ... objects) {
|
||||
for (DataObject o : objects) {
|
||||
if (o instanceof VolumeInfo && ((VolumeInfo) o).getPassphraseId() != null) {
|
||||
if (o instanceof VolumeInfo && (((VolumeInfo) o).getPassphraseId() != null || ((VolumeInfo) o).getKmsKeyId() != null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,7 +290,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
try {
|
||||
VolumeInfo vinfo = (VolumeInfo)data;
|
||||
String name = vinfo.getUuid();
|
||||
Long size = vinfo.getPassphraseId() == null ? vinfo.getSize() : vinfo.getSize() + 2097152;
|
||||
Long size = (vinfo.getPassphraseId() == null && vinfo.getKmsKeyId() == null) ? vinfo.getSize() : vinfo.getSize() + 2097152;
|
||||
Long vmId = vinfo.getInstanceId();
|
||||
|
||||
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
@ -309,7 +309,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
|
||||
updateVolume(dataStore, path, vinfo);
|
||||
|
||||
if (vinfo.getPassphraseId() != null) {
|
||||
if (vinfo.getPassphraseId() != null || vinfo.getKmsKeyId() != null) {
|
||||
VolumeObjectTO volume = updateVolumeObjectTO(vinfo, resp);
|
||||
answer = createEncryptedVolume(dataStore, data, vinfo, size, volume, null, true);
|
||||
} else {
|
||||
@ -360,11 +360,11 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
StorPoolSetVolumeEncryptionAnswer ans;
|
||||
EndPoint ep = null;
|
||||
if (parentName == null) {
|
||||
ep = selector.select(data, vinfo.getPassphraseId() != null);
|
||||
ep = selector.select(data, vinfo.getPassphraseId() != null || vinfo.getKmsKeyId() != null);
|
||||
} else {
|
||||
Long clusterId = StorPoolHelper.findClusterIdByGlobalId(parentName, clusterDao);
|
||||
if (clusterId == null) {
|
||||
ep = selector.select(data, vinfo.getPassphraseId() != null);
|
||||
ep = selector.select(data, vinfo.getPassphraseId() != null || vinfo.getKmsKeyId() != null);
|
||||
} else {
|
||||
List<HostVO> hosts = hostDao.findByClusterIdAndEncryptionSupport(clusterId);
|
||||
ep = CollectionUtils.isNotEmpty(hosts) ? RemoteHostEndPoint.getHypervisorHostEndPoint(hosts.get(0)) : ep;
|
||||
@ -558,10 +558,10 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
|
||||
private void tryToSnapshotVolumeBeforeDelete(VolumeInfo vinfo, DataStore dataStore, String name, SpConnectionDesc conn) {
|
||||
Integer deleteAfter = StorPoolConfigurationManager.DeleteAfterInterval.valueIn(dataStore.getId());
|
||||
if (deleteAfter != null && deleteAfter > 0 && vinfo.getPassphraseId() == null) {
|
||||
if (deleteAfter != null && deleteAfter > 0 && vinfo.getPassphraseId() == null && vinfo.getKmsKeyId() == null) {
|
||||
createTemporarySnapshot(vinfo, name, deleteAfter, conn);
|
||||
} else {
|
||||
StorPoolUtil.spLog("The volume [%s] is not marked to be snapshot. Check the global setting `storpool.delete.after.interval` or the volume is encrypted [%s]", name, deleteAfter, vinfo.getPassphraseId() != null);
|
||||
StorPoolUtil.spLog("The volume [%s] is not marked to be snapshot. Check the global setting `storpool.delete.after.interval` or the volume is encrypted [%s]", name, deleteAfter, vinfo.getPassphraseId() != null || vinfo.getKmsKeyId() != null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -862,7 +862,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
||||
vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
|
||||
|
||||
Long snapshotSize = templStoragePoolVO.getTemplateSize();
|
||||
boolean withoutEncryption = vinfo.getPassphraseId() == null;
|
||||
boolean withoutEncryption = vinfo.getPassphraseId() == null && vinfo.getKmsKeyId() == null;
|
||||
long size = withoutEncryption ? vinfo.getSize() : vinfo.getSize() + 2097152;
|
||||
if (snapshotSize != null && size < snapshotSize) {
|
||||
StorPoolUtil.spLog(String.format("provided size is too small for snapshot. Provided %d, snapshot %d. Using snapshot size", size, snapshotSize));
|
||||
|
||||
@ -298,7 +298,7 @@ public class StorPoolDataMotionStrategy implements DataMotionStrategy {
|
||||
|
||||
for (Map.Entry<VolumeInfo, DataStore> entry : volumeDataStoreMap.entrySet()) {
|
||||
VolumeInfo srcVolumeInfo = entry.getKey();
|
||||
if (srcVolumeInfo.getPassphraseId() != null) {
|
||||
if (srcVolumeInfo.getPassphraseId() != null || srcVolumeInfo.getKmsKeyId() != null) {
|
||||
throw new CloudRuntimeException(String.format("Cannot live migrate encrypted volume [%s] to StorPool", srcVolumeInfo.getVolume()));
|
||||
}
|
||||
DataStore destDataStore = entry.getValue();
|
||||
|
||||
@ -69,6 +69,11 @@
|
||||
<artifactId>cloud-framework-ca</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-kms</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-jobs</artifactId>
|
||||
|
||||
@ -2666,6 +2666,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
||||
Long clusterId = cmd.getClusterId();
|
||||
Long serviceOfferingId = cmd.getServiceOfferingId();
|
||||
Long diskOfferingId = cmd.getDiskOfferingId();
|
||||
Long kmsKeyId = cmd.getKmsKeyId();
|
||||
Boolean display = cmd.getDisplay();
|
||||
String state = cmd.getState();
|
||||
boolean shouldListSystemVms = shouldListSystemVms(cmd, caller.getId());
|
||||
@ -2702,6 +2703,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
||||
volumeSearchBuilder.and("uuid", volumeSearchBuilder.entity().getUuid(), SearchCriteria.Op.NNULL);
|
||||
volumeSearchBuilder.and("instanceId", volumeSearchBuilder.entity().getInstanceId(), SearchCriteria.Op.EQ);
|
||||
volumeSearchBuilder.and("dataCenterId", volumeSearchBuilder.entity().getDataCenterId(), SearchCriteria.Op.EQ);
|
||||
volumeSearchBuilder.and("kmsKeyId", volumeSearchBuilder.entity().getKmsKeyId(), SearchCriteria.Op.EQ);
|
||||
if (cmd.isEncrypted() != null) {
|
||||
if (cmd.isEncrypted()) {
|
||||
volumeSearchBuilder.and("encryptFormat", volumeSearchBuilder.entity().getEncryptFormat(), SearchCriteria.Op.NNULL);
|
||||
@ -2826,6 +2828,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
||||
if (vmInstanceId != null) {
|
||||
sc.setParameters("instanceId", vmInstanceId);
|
||||
}
|
||||
if (kmsKeyId != null) {
|
||||
sc.setParameters("kmsKeyId", kmsKeyId);
|
||||
}
|
||||
if (zoneId != null) {
|
||||
sc.setParameters("dataCenterId", zoneId);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// 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
|
||||
@ -29,6 +29,10 @@ import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||
import org.apache.cloudstack.api.response.VolumeResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.kms.KMSKekVersionVO;
|
||||
import org.apache.cloudstack.kms.KMSWrappedKeyVO;
|
||||
import org.apache.cloudstack.kms.dao.KMSKekVersionDao;
|
||||
import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
@ -64,6 +68,10 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation<VolumeJo
|
||||
@Inject
|
||||
private PrimaryDataStoreDao primaryDataStoreDao;
|
||||
@Inject
|
||||
private KMSWrappedKeyDao kmsWrappedKeyDao;
|
||||
@Inject
|
||||
private KMSKekVersionDao kmsKekVersionDao;
|
||||
@Inject
|
||||
private AnnotationDao annotationDao;
|
||||
|
||||
private final SearchBuilder<VolumeJoinVO> volSearch;
|
||||
@ -294,6 +302,18 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation<VolumeJo
|
||||
volResponse.setObjectName("volume");
|
||||
volResponse.setExternalUuid(volume.getExternalUuid());
|
||||
volResponse.setEncryptionFormat(volume.getEncryptionFormat());
|
||||
volResponse.setKmsKeyId(volume.getKmsKeyUuid());
|
||||
volResponse.setKmsKey(volume.getKmsKeyName());
|
||||
|
||||
if (volume.getKmsWrappedKeyId() != null) {
|
||||
KMSWrappedKeyVO wrappedKey = kmsWrappedKeyDao.findById(volume.getKmsWrappedKeyId());
|
||||
if (wrappedKey != null) {
|
||||
KMSKekVersionVO kekVersion = kmsKekVersionDao.findById(wrappedKey.getKekVersionId());
|
||||
if (kekVersion != null) {
|
||||
volResponse.setKmsKeyVersion(kekVersion.getVersionNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
return volResponse;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user