From 90e618798f37be16183be76fe34670a85b63218e Mon Sep 17 00:00:00 2001 From: "zhong.zhou" Date: Mon, 22 Jun 2026 18:47:21 +0800 Subject: [PATCH] [storage]: support encrypted scsi lun Expose encrypted SCSI LUN schema and SDK fields. Test: covered by premium EncryptScsiLunVmLifecycleCase. Change-Id: Ib25cca2a8805d8f96d7edd9f602e4a3218d493bb --- conf/db/zsv/V5.1.0__schema.sql | 9 + .../sdk/AttachScsiLunToVmInstanceAction.java | 3 + .../java/org/zstack/sdk/LunInventory.java | 8 + .../sdk/ScsiLunVmInstanceRefInventory.java | 8 + .../encrypt/VolumeEncryptedSecretHelper.java | 184 +++++++++++++----- 5 files changed, 165 insertions(+), 47 deletions(-) diff --git a/conf/db/zsv/V5.1.0__schema.sql b/conf/db/zsv/V5.1.0__schema.sql index 8e8b28157a6..40cfc270231 100644 --- a/conf/db/zsv/V5.1.0__schema.sql +++ b/conf/db/zsv/V5.1.0__schema.sql @@ -5,6 +5,8 @@ ALTER TABLE `zstack`.`VolumeEO` ADD COLUMN `encrypted` tinyint(1) NOT NULL DEFAU ALTER TABLE `zstack`.`VolumeSnapshotEO` ADD COLUMN `encrypted` tinyint(1) NOT NULL DEFAULT 0; ALTER TABLE `zstack`.`VolumeBackupVO` ADD COLUMN `encrypted` tinyint(1) NOT NULL DEFAULT 0; ALTER TABLE `zstack`.`VmInstanceEO` ADD COLUMN `vmEncryption` tinyint(1) NOT NULL DEFAULT 0; +ALTER TABLE `zstack`.`LunVO` ADD COLUMN `encrypted` tinyint(1) NOT NULL DEFAULT 0; +ALTER TABLE `zstack`.`ScsiLunVmInstanceRefVO` ADD COLUMN `encrypted` tinyint(1) NOT NULL DEFAULT 0; UPDATE `zstack`.`VolumeBackupVO` vb SET vb.`encrypted` = 1 @@ -31,6 +33,13 @@ SELECT uuid, name, description, type, volumeUuid, format, treeUuid, parentUuid, FROM `zstack`.`VolumeSnapshotEO` WHERE deleted IS NULL; +DROP VIEW IF EXISTS `zstack`.`ScsiLunVO`; +CREATE VIEW `zstack`.`ScsiLunVO` AS +SELECT uuid, name, wwid, vendor, model, wwn, serial, type, hctl, path, size, + state, source, multipathDeviceUuid, encrypted, createDate, lastOpDate +FROM `zstack`.`LunVO` +WHERE source IN ('iSCSI', 'fiberChannel'); + DROP VIEW IF EXISTS `zstack`.`VmInstanceVO`; CREATE VIEW `zstack`.`VmInstanceVO` AS SELECT uuid, name, description, zoneUuid, clusterUuid, imageUuid, hostUuid, internalId, diff --git a/sdk/src/main/java/org/zstack/sdk/AttachScsiLunToVmInstanceAction.java b/sdk/src/main/java/org/zstack/sdk/AttachScsiLunToVmInstanceAction.java index 14a0fc62a5a..d3268113d7f 100644 --- a/sdk/src/main/java/org/zstack/sdk/AttachScsiLunToVmInstanceAction.java +++ b/sdk/src/main/java/org/zstack/sdk/AttachScsiLunToVmInstanceAction.java @@ -34,6 +34,9 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public boolean disableMultiPathAttach = false; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.Boolean encrypted; + @Param(required = false) public java.util.List systemTags; diff --git a/sdk/src/main/java/org/zstack/sdk/LunInventory.java b/sdk/src/main/java/org/zstack/sdk/LunInventory.java index eabd6bab047..9c27353f2c8 100644 --- a/sdk/src/main/java/org/zstack/sdk/LunInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/LunInventory.java @@ -118,6 +118,14 @@ public java.lang.String getSource() { return this.source; } + public boolean encrypted; + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } + public boolean getEncrypted() { + return this.encrypted; + } + public java.sql.Timestamp createDate; public void setCreateDate(java.sql.Timestamp createDate) { this.createDate = createDate; diff --git a/sdk/src/main/java/org/zstack/sdk/ScsiLunVmInstanceRefInventory.java b/sdk/src/main/java/org/zstack/sdk/ScsiLunVmInstanceRefInventory.java index 1b53909960a..655c04a617e 100644 --- a/sdk/src/main/java/org/zstack/sdk/ScsiLunVmInstanceRefInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/ScsiLunVmInstanceRefInventory.java @@ -52,4 +52,12 @@ public boolean getAttachMultipath() { return this.attachMultipath; } + public boolean encrypted; + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } + public boolean getEncrypted() { + return this.encrypted; + } + } diff --git a/storage/src/main/java/org/zstack/storage/encrypt/VolumeEncryptedSecretHelper.java b/storage/src/main/java/org/zstack/storage/encrypt/VolumeEncryptedSecretHelper.java index 155dfdd8aeb..ab6cea56c1d 100644 --- a/storage/src/main/java/org/zstack/storage/encrypt/VolumeEncryptedSecretHelper.java +++ b/storage/src/main/java/org/zstack/storage/encrypt/VolumeEncryptedSecretHelper.java @@ -82,26 +82,31 @@ public class VolumeEncryptedSecretHelper { private PluginRegistry pluginRegistry; public EncryptedResourceKeyManager.ResourceKeyResult materializeDek(String volUuid, String kpUuid) { + return materializeResourceDek(volUuid, VolumeVO.class.getSimpleName(), kpUuid, "instantiate-volume"); + } + + public EncryptedResourceKeyManager.ResourceKeyResult materializeResourceDek(String resourceUuid, String resourceType, + String kpUuid, String purpose) { EncryptedResourceKeyManager.GetOrCreateResourceKeyContext ctx = new EncryptedResourceKeyManager.GetOrCreateResourceKeyContext(); - ctx.setResourceUuid(volUuid); - ctx.setResourceType(VolumeVO.class.getSimpleName()); + ctx.setResourceUuid(resourceUuid); + ctx.setResourceType(resourceType); ctx.setKeyProviderUuid(kpUuid); - ctx.setPurpose("instantiate-volume"); + ctx.setPurpose(purpose); FutureReturnValueCompletion completion = new FutureReturnValueCompletion(null); encryptedResourceKeyManager.getOrCreateKey(ctx, completion); completion.await(TimeUnit.MINUTES.toMillis(5)); if (!completion.isSuccess()) { throw new OperationFailureException(operr( - "failed to materialize encryption key for volume[uuid:%s]", volUuid) + "failed to materialize encryption key for resource[type:%s, uuid:%s]", resourceType, resourceUuid) .withCause(completion.getErrorCode())); } EncryptedResourceKeyManager.ResourceKeyResult result = completion.getResult(); if (result == null || StringUtils.isBlank(result.getDekBase64())) { throw new OperationFailureException(operr( - "key manager returned empty DEK for encrypted volume[uuid:%s]", volUuid)); + "key manager returned empty DEK for encrypted resource[type:%s, uuid:%s]", resourceType, resourceUuid)); } return result; } @@ -257,55 +262,67 @@ public String defineLibvirtSecretOnHost(String hostUuid, String vmUuid, String v public String defineLibvirtSecretOnHost(String hostUuid, String vmUuid, String volUuid, String dekBase64, Integer keyVersion, String secretUuid) { - if (StringUtils.isBlank(hostUuid) || StringUtils.isBlank(volUuid) || - StringUtils.isBlank(dekBase64) || keyVersion == null) { + String description = String.format("LUKS DEK for volume %s", volUuid); + String usageInstance = KVMConstant.volumeSecretUsageInstance(volUuid); + String result = defineLibvirtSecretForResourceOnHost(hostUuid, vmUuid, volUuid, dekBase64, + keyVersion, secretUuid, "volume", usageInstance, description); + + // Remember which host now owns this volume's libvirt secret so that + // expunge can clean it up later, even if the owning VM is gone by then. + // recreate=true overwrites any stale tag from a previous host. + try { + SystemTagCreator tc = VolumeSystemTags.VOLUME_LIBVIRT_SECRET_HOST.newSystemTagCreator(volUuid); + tc.setTagByTokens(Collections.singletonMap( + VolumeSystemTags.VOLUME_LIBVIRT_SECRET_HOST_TOKEN, hostUuid)); + tc.inherent = false; + tc.recreate = true; + tc.create(); + } catch (RuntimeException tagEx) { + // Tag write failure must not break the actual secret define -- the + // define already succeeded, the tag is for cleanup bookkeeping only. + logger.warn(String.format( + "failed to stamp VOLUME_LIBVIRT_SECRET_HOST tag on volume[uuid:%s] for host[uuid:%s]: %s", + volUuid, hostUuid, tagEx.getMessage())); + } + + return result; + } + + public String defineLibvirtSecretForResourceOnHost(String hostUuid, String vmUuid, String resourceUuid, + String dekBase64, Integer keyVersion, String secretUuid, + String purpose, String usageInstance, String description) { + if (StringUtils.isBlank(hostUuid) || StringUtils.isBlank(resourceUuid) || + StringUtils.isBlank(dekBase64) || keyVersion == null || + StringUtils.isBlank(purpose) || StringUtils.isBlank(usageInstance)) { throw new OperationFailureException(operr( - "defineLibvirtSecretOnHost requires non-blank hostUuid, volUuid, dekBase64 and a non-null keyVersion")); + "defineLibvirtSecretForResourceOnHost requires non-blank hostUuid, resourceUuid, dekBase64, purpose, usageInstance and a non-null keyVersion")); } SecretHostDefineMsg defineMsg = new SecretHostDefineMsg(); defineMsg.setHostUuid(hostUuid); defineMsg.setVmUuid(vmUuid); defineMsg.setDekBase64(dekBase64); - defineMsg.setPurpose("volume"); + defineMsg.setPurpose(purpose); defineMsg.setKeyVersion(keyVersion); - defineMsg.setUsageInstance(KVMConstant.volumeSecretUsageInstance(volUuid)); + defineMsg.setUsageInstance(usageInstance); if (StringUtils.isNotBlank(secretUuid)) { defineMsg.setSecretUuid(secretUuid); } - defineMsg.setDescription(String.format("LUKS DEK for volume %s", volUuid)); + defineMsg.setDescription(description); bus.makeTargetServiceIdByResourceUuid(defineMsg, HostConstant.SERVICE_ID, hostUuid); MessageReply reply = bus.call(defineMsg); if (!reply.isSuccess()) { throw new OperationFailureException(operr( - "failed to ensure libvirt secret for encrypted volume[uuid:%s] on host[uuid:%s]", - volUuid, hostUuid).withCause(reply.getError())); + "failed to ensure libvirt secret for encrypted resource[uuid:%s] on host[uuid:%s]", + resourceUuid, hostUuid).withCause(reply.getError())); } SecretHostDefineReply r = reply.castReply(); if (StringUtils.isBlank(r.getSecretUuid())) { throw new OperationFailureException(operr( - "ensure volume LUKS secret on host succeeded but secretUuid is empty, host[uuid:%s]", + "ensure resource LUKS secret on host succeeded but secretUuid is empty, host[uuid:%s]", hostUuid)); } - // Remember which host now owns this volume's libvirt secret so that - // expunge can clean it up later, even if the owning VM is gone by then. - // recreate=true overwrites any stale tag from a previous host. - try { - SystemTagCreator tc = VolumeSystemTags.VOLUME_LIBVIRT_SECRET_HOST.newSystemTagCreator(volUuid); - tc.setTagByTokens(Collections.singletonMap( - VolumeSystemTags.VOLUME_LIBVIRT_SECRET_HOST_TOKEN, hostUuid)); - tc.inherent = false; - tc.recreate = true; - tc.create(); - } catch (RuntimeException tagEx) { - // Tag write failure must not break the actual secret define -- the - // define already succeeded, the tag is for cleanup bookkeeping only. - logger.warn(String.format( - "failed to stamp VOLUME_LIBVIRT_SECRET_HOST tag on volume[uuid:%s] for host[uuid:%s]: %s", - volUuid, hostUuid, tagEx.getMessage())); - } - return r.getSecretUuid(); } @@ -327,19 +344,29 @@ public String defineSecretFromBinding(String hostUuid, String vmUuid, String vol } public String defineSecretFromBinding(String hostUuid, String vmUuid, String volUuid, String kpUuid, String secretUuid) { + return defineSecretFromBindingForResource(hostUuid, vmUuid, volUuid, VolumeVO.class.getSimpleName(), kpUuid, + "volume", KVMConstant.volumeSecretUsageInstance(volUuid), secretUuid); + } + + public String defineSecretFromBindingForResource(String hostUuid, String vmUuid, String resourceUuid, + String resourceType, String kpUuid, String secretPurpose, + String usageInstance, String secretUuid) { if (StringUtils.isBlank(kpUuid)) { throw new OperationFailureException(operr( - "encrypted volume[uuid:%s] has no key provider binding; cannot define libvirt secret on host[uuid:%s]", - volUuid, hostUuid)); + "encrypted resource[type:%s, uuid:%s] has no key provider binding; cannot define libvirt secret on host[uuid:%s]", + resourceType, resourceUuid, hostUuid)); } - EncryptedResourceKeyManager.ResourceKeyResult keyResult = materializeDek(volUuid, kpUuid); + EncryptedResourceKeyManager.ResourceKeyResult keyResult = materializeResourceDek(resourceUuid, resourceType, + kpUuid, "define-" + secretPurpose + "-secret"); String dekBase64 = keyResult.getDekBase64(); if (StringUtils.isBlank(dekBase64)) { throw new OperationFailureException(operr( - "encrypted volume[uuid:%s]: key manager returned empty DEK for libvirt secret", - volUuid)); + "encrypted resource[type:%s, uuid:%s]: key manager returned empty DEK for libvirt secret", + resourceType, resourceUuid)); } - return defineLibvirtSecretOnHost(hostUuid, vmUuid, volUuid, dekBase64, keyResult.getKeyVersion(), secretUuid); + String description = String.format("LUKS DEK for %s %s", resourceType, resourceUuid); + return defineLibvirtSecretForResourceOnHost(hostUuid, vmUuid, resourceUuid, dekBase64, + keyResult.getKeyVersion(), secretUuid, secretPurpose, usageInstance, description); } /** @@ -350,12 +377,18 @@ public String defineSecretFromBinding(String hostUuid, String vmUuid, String vol * contract, but key-agent does not include it in volume secret usage names. */ public String getSecretOnHost(String hostUuid, String vmUuid, String volUuid, Integer keyVersion) { + return getSecretForResourceOnHost(hostUuid, vmUuid, volUuid, keyVersion, + "volume", KVMConstant.volumeSecretUsageInstance(volUuid)); + } + + public String getSecretForResourceOnHost(String hostUuid, String vmUuid, String resourceUuid, Integer keyVersion, + String purpose, String usageInstance) { SecretHostGetMsg msg = new SecretHostGetMsg(); msg.setHostUuid(hostUuid); msg.setVmUuid(vmUuid); - msg.setPurpose("volume"); + msg.setPurpose(purpose); msg.setKeyVersion(keyVersion); - msg.setUsageInstance(KVMConstant.volumeSecretUsageInstance(volUuid)); + msg.setUsageInstance(usageInstance); bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, hostUuid); MessageReply reply = bus.call(msg); @@ -368,28 +401,35 @@ public String getSecretOnHost(String hostUuid, String vmUuid, String volUuid, In return null; } throw new OperationFailureException(operr( - "failed to get libvirt LUKS secret on host[uuid:%s] vm[uuid:%s] volume[uuid:%s] keyVersion[%s]: %s", - hostUuid, vmUuid, volUuid, keyVersion, err)); + "failed to get libvirt LUKS secret on host[uuid:%s] vm[uuid:%s] resource[uuid:%s] keyVersion[%s]: %s", + hostUuid, vmUuid, resourceUuid, keyVersion, err)); } public void deleteSecretOnHostBestEffort(String hostUuid, String vmUuid, String volUuid, Integer keyVersion) { + deleteSecretForResourceOnHostBestEffort(hostUuid, vmUuid, volUuid, keyVersion, + "volume", KVMConstant.volumeSecretUsageInstance(volUuid)); + } + + public void deleteSecretForResourceOnHostBestEffort(String hostUuid, String vmUuid, String resourceUuid, + Integer keyVersion, String purpose, String usageInstance) { if (StringUtils.isBlank(hostUuid) || StringUtils.isBlank(vmUuid) - || StringUtils.isBlank(volUuid) || keyVersion == null) { + || StringUtils.isBlank(resourceUuid) || keyVersion == null + || StringUtils.isBlank(purpose) || StringUtils.isBlank(usageInstance)) { return; } SecretHostDeleteMsg msg = new SecretHostDeleteMsg(); msg.setHostUuid(hostUuid); msg.setVmUuid(vmUuid); - msg.setPurpose("volume"); + msg.setPurpose(purpose); msg.setKeyVersion(keyVersion); - msg.setUsageInstance(KVMConstant.volumeSecretUsageInstance(volUuid)); + msg.setUsageInstance(usageInstance); bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, hostUuid); MessageReply reply = bus.call(msg); if (!reply.isSuccess()) { logger.warn(String.format( - "best-effort delete libvirt LUKS secret failed for volume[uuid:%s] on host[uuid:%s] vm[uuid:%s]: %s", - volUuid, hostUuid, vmUuid, reply.getError())); + "best-effort delete libvirt LUKS secret failed for resource[uuid:%s] on host[uuid:%s] vm[uuid:%s]: %s", + resourceUuid, hostUuid, vmUuid, reply.getError())); } } @@ -414,6 +454,23 @@ public String resolveOrDefineSecretForVolume(String hostUuid, String vmUuid, Str return defineSecretFromBinding(hostUuid, vmUuid, volUuid, kpUuid); } + public String resolveOrDefineSecretForResource(String hostUuid, String vmUuid, String resourceUuid, + String resourceType, String kpUuid, Integer keyVersion, + String secretPurpose, String usageInstance) { + if (keyVersion == null) { + throw new OperationFailureException(operr( + "encrypted resource[type:%s, uuid:%s] has no key version bound; cannot resolve libvirt LUKS secret on host[uuid:%s] for vm[uuid:%s]", + resourceType, resourceUuid, hostUuid, vmUuid)); + } + String secretUuid = getSecretForResourceOnHost(hostUuid, vmUuid, resourceUuid, keyVersion, + secretPurpose, usageInstance); + if (StringUtils.isNotBlank(secretUuid)) { + return secretUuid; + } + return defineSecretFromBindingForResource(hostUuid, vmUuid, resourceUuid, resourceType, kpUuid, + secretPurpose, usageInstance, null); + } + private String resolveVolumeLibvirtSecretUuidFromDomainXml(String hostUuid, String vmUuid, String volUuid) { KVMAgentCommands.ResolveVolumeLibvirtSecretCmd cmd = new KVMAgentCommands.ResolveVolumeLibvirtSecretCmd(); cmd.setVmUuid(vmUuid); @@ -484,4 +541,37 @@ public String resolveOrDefineSecretForVolumeMigration(String srcHostUuid, String String kpUuid = volumeEncryptedResourceKeyBackend.findKeyProviderUuidByVolume(volUuid); return defineSecretFromBinding(dstHostUuid, vmUuid, volUuid, kpUuid, sourceSecretUuid); } + + public String resolveOrDefineSecretForResourceMigration(String srcHostUuid, String dstHostUuid, String vmUuid, + String resourceUuid, String resourceType, String kpUuid, + Integer keyVersion, String secretPurpose, + String usageInstance) { + if (StringUtils.isBlank(srcHostUuid) || StringUtils.isBlank(dstHostUuid) + || StringUtils.isBlank(vmUuid) || StringUtils.isBlank(resourceUuid)) { + throw new OperationFailureException(operr( + "resolve migration LUKS secret requires non-blank srcHostUuid, dstHostUuid, vmUuid and resourceUuid")); + } + + if (keyVersion == null) { + throw new OperationFailureException(operr( + "encrypted resource[type:%s, uuid:%s] has no key version bound; cannot resolve migration LUKS secret for vm[uuid:%s]", + resourceType, resourceUuid, vmUuid)); + } + + String sourceSecretUuid = getSecretForResourceOnHost(srcHostUuid, vmUuid, resourceUuid, keyVersion, + secretPurpose, usageInstance); + if (StringUtils.isBlank(sourceSecretUuid)) { + sourceSecretUuid = defineSecretFromBindingForResource(srcHostUuid, vmUuid, resourceUuid, resourceType, + kpUuid, secretPurpose, usageInstance, null); + } + + String destSecretUuid = getSecretForResourceOnHost(dstHostUuid, vmUuid, resourceUuid, keyVersion, + secretPurpose, usageInstance); + if (StringUtils.equals(destSecretUuid, sourceSecretUuid)) { + return destSecretUuid; + } + + return defineSecretFromBindingForResource(dstHostUuid, vmUuid, resourceUuid, resourceType, + kpUuid, secretPurpose, usageInstance, sourceSecretUuid); + } }