diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/DummyEncryptedResourceKeyManager.java b/compute/src/main/java/org/zstack/compute/vm/devices/DummyEncryptedResourceKeyManager.java index e7fb4cac4e2..0bd6a8eaaae 100644 --- a/compute/src/main/java/org/zstack/compute/vm/devices/DummyEncryptedResourceKeyManager.java +++ b/compute/src/main/java/org/zstack/compute/vm/devices/DummyEncryptedResourceKeyManager.java @@ -1,5 +1,6 @@ package org.zstack.compute.vm.devices; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.keyprovider.EncryptedResourceKeyManager; import org.zstack.header.core.Completion; import org.zstack.header.core.ReturnValueCompletion; @@ -19,6 +20,13 @@ public void getOrCreateKey(GetOrCreateResourceKeyContext ctx, completion.fail(operr("crypto module is not installed, cannot manage resource encryption keys")); } + @Override + public ResourceKeyResult getKey(GetOrCreateResourceKeyContext ctx) { + logger.warn(String.format("crypto module not installed, cannot get resource key for %s[uuid:%s]", + ctx.getResourceType(), ctx.getResourceUuid())); + throw new OperationFailureException(operr("crypto module is not installed, cannot manage resource encryption keys")); + } + @Override public void rollbackCreatedKey(ResourceKeyResult result, Completion completion) { completion.success(); diff --git a/header/src/main/java/org/zstack/header/keyprovider/EncryptedResourceKeyManager.java b/header/src/main/java/org/zstack/header/keyprovider/EncryptedResourceKeyManager.java index 8a61c02ce83..ffa38675402 100644 --- a/header/src/main/java/org/zstack/header/keyprovider/EncryptedResourceKeyManager.java +++ b/header/src/main/java/org/zstack/header/keyprovider/EncryptedResourceKeyManager.java @@ -26,11 +26,34 @@ public interface EncryptedResourceKeyManager { void getOrCreateKey(GetOrCreateResourceKeyContext ctx, ReturnValueCompletion completion); + /** + * Load the existing resource encryption key material only. + *

+ * Requires an {@code EncryptedResourceKeyRef} row and a usable secret reference already stored + * for the resource. Does not insert a ref row and does not call + * key-tool/KMS create APIs. + *

+ * The implementation may still call key-tool/KMS get/unwrap for the existing + * secret ref in order to return the plaintext DEK (for example defining the secret on the destination + * host during hot migration). That RPC is read-side materialization, not secret creation. + *

+ * On success, the implementation may update {@code EncryptedResourceKeyRef} provider columns to match + * the resolved key provider when they have drifted (same behavior as the existing-key branch of + * {@link #getOrCreateKey}). + * + * @param ctx same fields as {@link #getOrCreateKey}; identifies resource and provider + * @return {@link ResourceKeyResult} with {@code createdNewKey == false} on success + * @throws org.zstack.header.errorcode.OperationFailureException when the key cannot be loaded + */ + ResourceKeyResult getKey(GetOrCreateResourceKeyContext ctx); + /** * Roll back a newly created resource key during upper-layer workflow rollback. *

- * If the key record already existed before creation, implementation should restore it - * to its previous empty-placeholder state instead of deleting the relationship. + * When {@link ResourceKeyResult#isCreatedNewKey()} is true, the implementation deletes the + * key-tool secret if one was materialized, then removes the {@code EncryptedResourceKeyRef} row + * for the resource (same storage effect as detaching the key provider from the resource). + * When {@code createdNewKey} is false (existing secret was reused), this is a no-op. */ void rollbackCreatedKey(ResourceKeyResult result, Completion completion); @@ -91,7 +114,6 @@ class ResourceKeyResult { private String dekBase64; private String secretRef; private boolean createdNewKey; - private boolean refExistedBeforeCreate; public String getResourceUuid() { return resourceUuid; @@ -156,13 +178,5 @@ public boolean isCreatedNewKey() { public void setCreatedNewKey(boolean createdNewKey) { this.createdNewKey = createdNewKey; } - - public boolean isRefExistedBeforeCreate() { - return refExistedBeforeCreate; - } - - public void setRefExistedBeforeCreate(boolean refExistedBeforeCreate) { - this.refExistedBeforeCreate = refExistedBeforeCreate; - } } } diff --git a/header/src/main/java/org/zstack/header/secret/SecretHostDefineMsg.java b/header/src/main/java/org/zstack/header/secret/SecretHostDefineMsg.java index 2421e304d8d..d3258f249fd 100644 --- a/header/src/main/java/org/zstack/header/secret/SecretHostDefineMsg.java +++ b/header/src/main/java/org/zstack/header/secret/SecretHostDefineMsg.java @@ -16,6 +16,8 @@ public class SecretHostDefineMsg extends NeedReplyMessage implements HostMessage private String vmUuid; private String purpose; private Integer keyVersion; + private String usageInstance; + private String secretUuid; private String description; @Override @@ -59,6 +61,22 @@ public void setKeyVersion(Integer keyVersion) { this.keyVersion = keyVersion; } + public String getUsageInstance() { + return usageInstance; + } + + public void setUsageInstance(String usageInstance) { + this.usageInstance = usageInstance; + } + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } + public String getDescription() { return description; } diff --git a/header/src/main/java/org/zstack/header/secret/SecretHostDeleteMsg.java b/header/src/main/java/org/zstack/header/secret/SecretHostDeleteMsg.java index 7c119e3183b..f9502d16c67 100644 --- a/header/src/main/java/org/zstack/header/secret/SecretHostDeleteMsg.java +++ b/header/src/main/java/org/zstack/header/secret/SecretHostDeleteMsg.java @@ -8,6 +8,7 @@ public class SecretHostDeleteMsg extends NeedReplyMessage implements HostMessage private String vmUuid; private String purpose; private Integer keyVersion; + private String usageInstance; @Override public String getHostUuid() { @@ -41,4 +42,12 @@ public Integer getKeyVersion() { public void setKeyVersion(Integer keyVersion) { this.keyVersion = keyVersion; } + + public String getUsageInstance() { + return usageInstance; + } + + public void setUsageInstance(String usageInstance) { + this.usageInstance = usageInstance; + } } diff --git a/header/src/main/java/org/zstack/header/secret/SecretHostGetMsg.java b/header/src/main/java/org/zstack/header/secret/SecretHostGetMsg.java index ecb787956cb..f63284880e0 100644 --- a/header/src/main/java/org/zstack/header/secret/SecretHostGetMsg.java +++ b/header/src/main/java/org/zstack/header/secret/SecretHostGetMsg.java @@ -11,6 +11,7 @@ public class SecretHostGetMsg extends NeedReplyMessage implements HostMessage { private String vmUuid; private String purpose; private Integer keyVersion; + private String usageInstance; @Override public String getHostUuid() { @@ -44,4 +45,12 @@ public Integer getKeyVersion() { public void setKeyVersion(Integer keyVersion) { this.keyVersion = keyVersion; } + + public String getUsageInstance() { + return usageInstance; + } + + public void setUsageInstance(String usageInstance) { + this.usageInstance = usageInstance; + } } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 663357a193d..5b675f4ff72 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -411,8 +411,9 @@ public static class SecretHostDefineCmd extends AgentCommand { private String vmUuid; private String purpose; private Integer keyVersion; - private String description; private String usageInstance; + private String secretUuid; + private String description; public String getEncryptedDek() { return encryptedDek; @@ -446,14 +447,6 @@ public void setKeyVersion(Integer keyVersion) { this.keyVersion = keyVersion; } - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - public String getUsageInstance() { return usageInstance; } @@ -461,6 +454,22 @@ public String getUsageInstance() { public void setUsageInstance(String usageInstance) { this.usageInstance = usageInstance; } + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } } public static class SecretHostDefineResponse extends AgentResponse { diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 3b3b3202882..994380b326f 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -5320,8 +5320,9 @@ public void handle(ErrorCode errCode, Map data) { private void handle(SecretHostGetMsg msg) { SecretHostGetReply reply = new SecretHostGetReply(); - if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose()) || msg.getKeyVersion() == null) { - reply.setError(operr("vmUuid, purpose and keyVersion are required for get secret")); + if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose()) || + msg.getKeyVersion() == null || StringUtils.isBlank(msg.getUsageInstance())) { + reply.setError(operr("vmUuid, purpose, keyVersion and usageInstance are required for get secret")); bus.reply(msg, reply); return; } @@ -5331,7 +5332,7 @@ private void handle(SecretHostGetMsg msg) { cmd.setVmUuid(msg.getVmUuid()); cmd.setPurpose(msg.getPurpose()); cmd.setKeyVersion(msg.getKeyVersion()); - cmd.setUsageInstance(KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM); + cmd.setUsageInstance(msg.getUsageInstance()); Map headers = new HashMap<>(); headers.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, getSelf().getUuid()); Http http = new Http<>(url, cmd, KVMAgentCommands.SecretHostGetResponse.class); @@ -5372,8 +5373,9 @@ private void handle(SecretHostDefineMsg msg) { bus.reply(msg, reply); return; } - if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose()) || msg.getKeyVersion() == null) { - reply.setError(operr("vmUuid, purpose and keyVersion are required for ensure secret")); + if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose()) || + msg.getKeyVersion() == null || StringUtils.isBlank(msg.getUsageInstance())) { + reply.setError(operr("vmUuid, purpose, keyVersion and usageInstance are required for ensure secret")); bus.reply(msg, reply); return; } @@ -5452,8 +5454,9 @@ private void handle(SecretHostDefineMsg msg) { cmd.setVmUuid(msg.getVmUuid()); cmd.setPurpose(msg.getPurpose()); cmd.setKeyVersion(msg.getKeyVersion()); + cmd.setSecretUuid(msg.getSecretUuid()); cmd.setDescription(msg.getDescription() != null ? msg.getDescription() : ""); - cmd.setUsageInstance(KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM); + cmd.setUsageInstance(msg.getUsageInstance()); Map headers = new HashMap<>(); headers.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, getSelf().getUuid()); Http http = new Http<>(url, cmd, KVMAgentCommands.SecretHostDefineResponse.class); @@ -5486,13 +5489,9 @@ public Class getReturnClass() { private void handle(SecretHostDeleteMsg msg) { SecretHostDeleteReply reply = new SecretHostDeleteReply(); - if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose())) { - reply.setError(operr("vmUuid and purpose are required for delete secret")); - bus.reply(msg, reply); - return; - } - if (msg.getKeyVersion() == null) { - reply.setError(operr("keyVersion is required for delete secret")); + if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose()) || + StringUtils.isBlank(msg.getUsageInstance()) || msg.getKeyVersion() == null) { + reply.setError(operr("vmUuid, purpose, keyVersion and usageInstance are required for delete secret")); bus.reply(msg, reply); return; } @@ -5502,7 +5501,7 @@ private void handle(SecretHostDeleteMsg msg) { cmd.setVmUuid(msg.getVmUuid()); cmd.setPurpose(msg.getPurpose()); cmd.setKeyVersion(msg.getKeyVersion()); - cmd.setUsageInstance(KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM); + cmd.setUsageInstance(msg.getUsageInstance()); Map headers = new HashMap<>(); headers.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, getSelf().getUuid()); Http http = new Http<>(url, cmd, KVMAgentCommands.SecretHostDeleteResponse.class); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java index eaa846aeb5c..c6302660f1d 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.zstack.compute.vm.VmGlobalConfig; import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend; +import org.zstack.compute.vm.devices.VmTpmManager; import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend.CloneEncryptedResourceKeyContext; import org.zstack.core.Platform; import org.zstack.core.cloudbus.CloudBus; @@ -14,6 +15,7 @@ import org.zstack.core.db.SQL; import org.zstack.core.workflow.SimpleFlowChain; import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.core.workflow.FlowDoneHandler; import org.zstack.header.core.workflow.FlowErrorHandler; import org.zstack.header.core.workflow.Flow; @@ -42,6 +44,7 @@ import org.zstack.header.secret.SecretHostDefineReply; import org.zstack.header.tpm.message.RemoveTpmMsg; import org.zstack.header.vm.PreVmInstantiateResourceExtensionPoint; +import org.zstack.header.vm.VmInstanceConstant; import org.zstack.header.vm.VmInstanceInventory; import org.zstack.header.vm.VmInstanceSpec; import org.zstack.header.vm.VmInstanceState; @@ -183,6 +186,8 @@ public void preInstantiateVmResource(VmInstanceSpec spec, Completion completion) context.backupFileUuid = tpmSpec.getBackupFileUuid(); // maybe null context.providerUuid = resourceKeyBackend.findKeyProviderUuidByTpm(context.tpmUuid); context.keyVersion = resourceKeyBackend.findKeyVersionByTpm(context.tpmUuid); + context.instantiateForNewVm = spec.getCurrentVmOperation() == VmInstanceConstant.VmOperation.NewCreate; + context.enableKeyProvider = !VmGlobalConfig.ALLOWED_TPM_VM_WITHOUT_KMS.value(Boolean.class); final SimpleFlowChain chain = new SimpleFlowChain(); chain.setName("prepare-tpm-resources-for-vm-" + spec.getVmInventory().getUuid()); @@ -214,8 +219,7 @@ public void fail(ErrorCode errorCode) { @Override public boolean skip(Map data) { - return StringUtils.isBlank(findSourceTpmUuidFromSnapshotTpmBackupFile(context.backupFileUuid)) || - VmGlobalConfig.ALLOWED_TPM_VM_WITHOUT_KMS.value(Boolean.class); + return !context.enableKeyProvider; } @Override @@ -243,6 +247,9 @@ public void fail(ErrorCode errorCode) { }); } + // Use clone op above and will not set rollback flag TpmSpec.resourceKeyCreatedNew + // to true, so use flow rollback instead preReleaseVmResource rollback. And we + // definitely don't need to delete keytool secret on shapshot case. @Override public void rollback(FlowRollback trigger, Map data) { if (StringUtils.isNotBlank(context.backupFileUuid)) { @@ -256,72 +263,36 @@ public void rollback(FlowRollback trigger, Map data) { trigger.rollback(); } }).then(new NoRollbackFlow() { - String __name__ = "ensure-resource-key-ref"; + String __name__ = "get-secret-on-host-first"; @Override public boolean skip(Map data) { - boolean shouldSkip = StringUtils.isBlank(context.providerUuid); - if (shouldSkip) { - logger.info(String.format( - "skip ensure-resource-key-ref for tpm[uuid:%s] due to missing key provider binding, try host secret first", - context.tpmUuid)); - } - return shouldSkip; + return !context.enableKeyProvider; } @Override public void run(FlowTrigger trigger, Map data) { + if (context.instantiateForNewVm && context.keyVersion == null) { + trigger.next(); + return; + } if (StringUtils.isBlank(context.providerUuid)) { - trigger.fail(operr("missing TPM resource key binding for tpm[uuid:%s], attachKeyProviderToTpm must run before create-dek", context.tpmUuid)); + trigger.fail(operr("missing TPM resource key binding for tpm[uuid:%s], attachKeyProviderToTpm must run before get-secret-on-host", + context.tpmUuid)); return; } - - if (context.keyVersion != null) { - trigger.next(); + if (!context.instantiateForNewVm && context.keyVersion == null) { + trigger.fail(operr("missing keyVersion for tpm[uuid:%s] before get secret on host", context.tpmUuid)); return; } - - GetOrCreateResourceKeyContext keyCtx = new GetOrCreateResourceKeyContext(); - keyCtx.setResourceUuid(context.tpmUuid); - keyCtx.setResourceType(TpmVO.class.getSimpleName()); - keyCtx.setKeyProviderUuid(context.providerUuid); - keyCtx.setPurpose("vtpm"); - - resourceKeyManager.getOrCreateKey(keyCtx, new ReturnValueCompletion(trigger) { - @Override - public void success(ResourceKeyResult result) { - tpmSpec.setResourceKeyCreatedNew(result.isCreatedNewKey()); - tpmSpec.setResourceKeyProviderUuid(result.getKeyProviderUuid()); - context.dekBase64 = result.getDekBase64(); - context.keyVersion = result.getKeyVersion(); - if (context.keyVersion == null) { - trigger.fail(operr("missing keyVersion for tpm[uuid:%s] after getOrCreateKey", context.tpmUuid)); - return; - } - trigger.next(); - } - - @Override - public void fail(ErrorCode errorCode) { - trigger.fail(errorCode); - } - }); - } - }).then(new NoRollbackFlow() { - String __name__ = "get-secret-on-host"; - - @Override - public boolean skip(Map data) { - return context.keyVersion == null; - } - - @Override - public void run(FlowTrigger trigger, Map data) { + // NewCreate cloned from an existing TPM may already carry keyVersion; allow it. + // For non-NewCreate, keyVersion must exist (validated above). SecretHostGetMsg innerMsg = new SecretHostGetMsg(); innerMsg.setHostUuid(spec.getDestHost().getUuid()); innerMsg.setVmUuid(spec.getVmInventory().getUuid()); innerMsg.setPurpose("vtpm"); innerMsg.setKeyVersion(context.keyVersion); + innerMsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); bus.makeTargetServiceIdByResourceUuid(innerMsg, HostConstant.SERVICE_ID, innerMsg.getHostUuid()); bus.send(innerMsg, new CloudBusCallBack(trigger) { @Override @@ -344,11 +315,11 @@ public void run(MessageReply reply) { }); } }).then(new NoRollbackFlow() { - String __name__ = "load-dek-for-host"; + String __name__ = "get-or-create-key-and-dek"; @Override public boolean skip(Map data) { - return StringUtils.isNotBlank(spec.getDevicesSpec().getTpm().getSecretUuid()) || context.dekBase64 != null; + return !context.enableKeyProvider; } @Override @@ -367,7 +338,7 @@ public void success(ResourceKeyResult result) { context.dekBase64 = result.getDekBase64(); context.keyVersion = result.getKeyVersion(); if (context.keyVersion == null) { - trigger.fail(operr("missing keyVersion for tpm[uuid:%s] before ensure secret", context.tpmUuid)); + trigger.fail(operr("missing keyVersion for tpm[uuid:%s] after getOrCreateKey", context.tpmUuid)); return; } trigger.next(); @@ -384,13 +355,13 @@ public void fail(ErrorCode errorCode) { @Override public boolean skip(Map data) { - return context.dekBase64 == null || StringUtils.isNotBlank(spec.getDevicesSpec().getTpm().getSecretUuid()); + return !context.enableKeyProvider; } @Override public void run(FlowTrigger trigger, Map data) { - if (context.keyVersion == null) { - trigger.fail(operr("missing keyVersion for tpm[uuid:%s] before define-secret-on-host", context.tpmUuid)); + if (context.dekBase64 == null) { + trigger.fail(operr("missing dekBase64 for tpm[uuid:%s] before define-secret-on-host", context.tpmUuid)); return; } @@ -400,6 +371,7 @@ public void run(FlowTrigger trigger, Map data) { innerMsg.setDekBase64(context.dekBase64); innerMsg.setPurpose("vtpm"); innerMsg.setKeyVersion(context.keyVersion); + innerMsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); innerMsg.setDescription("Define secret for VM " + spec.getVmInventory().getUuid()); bus.makeTargetServiceIdByResourceUuid(innerMsg, HostConstant.SERVICE_ID, innerMsg.getHostUuid()); bus.send(innerMsg, new CloudBusCallBack(trigger) { @@ -442,11 +414,13 @@ static class PrepareTpmStateHostFileContext { } static class PrepareTpmResourceContext { + boolean enableKeyProvider; String tpmUuid; String backupFileUuid; String providerUuid; Integer keyVersion; String dekBase64; + boolean instantiateForNewVm; void clearSensitiveData() { dekBase64 = null; @@ -466,8 +440,6 @@ public void preReleaseVmResource(VmInstanceSpec spec, Completion completion) { result.setResourceType(TpmVO.class.getSimpleName()); result.setKeyProviderUuid(tpmSpec.getResourceKeyProviderUuid()); result.setCreatedNewKey(true); - // TPM resource key creation requires attachKeyProviderToTpm to create a placeholder ref first. - result.setRefExistedBeforeCreate(true); resourceKeyManager.rollbackCreatedKey(result, new Completion(completion) { @Override @@ -562,6 +534,96 @@ private String findSourceTpmUuidFromSnapshotTpmBackupFile(String tpmBackupFileUu .findValue(); } + @Override + public void preMigrateVm(VmInstanceInventory inv, String destHostUuid, Completion completion) { + if (inv == null || StringUtils.isBlank(destHostUuid)) { + completion.success(); + return; + } + String srcHostUuid = inv.getHostUuid(); + if (StringUtils.isBlank(srcHostUuid)) { + completion.success(); + return; + } + try { + VtpmMigratePreAgentContext ctx = new VtpmMigratePreAgentContext(inv.getUuid(), srcHostUuid, destHostUuid); + ctx.setEnableKeyProvider(!VmGlobalConfig.ALLOWED_TPM_VM_WITHOUT_KMS.value(Boolean.class)); + prepareVtpmSecretOnHostsBeforeMigrate(ctx); + completion.success(); + } catch (OperationFailureException e) { + completion.fail(e.getErrorCode()); + } + } + + private void prepareVtpmSecretOnHostsBeforeMigrate(VtpmMigratePreAgentContext ctx) { + String tpmUuid = VmTpmManager.findTpmUuidForVmOrNull(ctx.getVmUuid()); + if (StringUtils.isBlank(tpmUuid)) { + return; + } + if (!ctx.isEnableKeyProvider()) { + return; + } + ctx.setTpmUuid(tpmUuid); + ctx.setProviderUuid(resourceKeyBackend.findKeyProviderUuidByTpm(tpmUuid)); + ctx.setProviderName(resourceKeyBackend.findKeyProviderNameByTpm(tpmUuid)); + if (StringUtils.isBlank(ctx.getProviderUuid()) && StringUtils.isBlank(ctx.getProviderName())) { + throw new OperationFailureException(operr("missing TPM resource key binding for tpm[uuid:%s] before migrate", tpmUuid)); + } + ctx.setKeyVersion(resourceKeyBackend.findKeyVersionByTpm(tpmUuid)); + if (ctx.getKeyVersion() == null) { + throw new OperationFailureException(operr("cannot find keyVersion for tpm[uuid:%s] before migrate", tpmUuid)); + } + + GetOrCreateResourceKeyContext keyCtx = new GetOrCreateResourceKeyContext(); + keyCtx.setResourceUuid(ctx.getTpmUuid()); + keyCtx.setResourceType(TpmVO.class.getSimpleName()); + keyCtx.setKeyProviderUuid(ctx.getProviderUuid()); + keyCtx.setKeyProviderName(ctx.getProviderName()); + keyCtx.setPurpose("vtpm"); + ResourceKeyResult result = resourceKeyManager.getKey(keyCtx); + if (StringUtils.isBlank(result.getDekBase64())) { + throw new OperationFailureException(operr("missing DEK for tpm[uuid:%s] after getKey before migrate", ctx.getTpmUuid())); + } + ctx.setResourceKeyResult(result); + + SecretHostGetMsg getMsg = new SecretHostGetMsg(); + getMsg.setHostUuid(ctx.getSrcHostUuid()); + getMsg.setVmUuid(ctx.getVmUuid()); + getMsg.setPurpose("vtpm"); + getMsg.setKeyVersion(ctx.getKeyVersion()); + getMsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); + bus.makeTargetServiceIdByResourceUuid(getMsg, HostConstant.SERVICE_ID, getMsg.getHostUuid()); + MessageReply getReply = bus.call(getMsg); + if (!getReply.isSuccess()) { + throw new OperationFailureException(getReply.getError() != null ? getReply.getError() + : operr("get secret on source host failed before migrate")); + } + SecretHostGetReply getR = getReply.castReply(); + if (StringUtils.isBlank(getR.getSecretUuid())) { + throw new OperationFailureException(operr("failed to get source secret uuid before migrate: empty secretUuid")); + } + ctx.setSourceSecretUuid(getR.getSecretUuid()); + + ResourceKeyResult keyResult = ctx.getResourceKeyResult(); + if (keyResult == null || StringUtils.isBlank(keyResult.getDekBase64())) { + throw new OperationFailureException(operr("missing DEK for tpm[uuid:%s] before ensure secret on destination", ctx.getTpmUuid())); + } + SecretHostDefineMsg defMsg = new SecretHostDefineMsg(); + defMsg.setHostUuid(ctx.getDstHostUuid()); + defMsg.setVmUuid(ctx.getVmUuid()); + defMsg.setDekBase64(keyResult.getDekBase64()); + defMsg.setPurpose("vtpm"); + defMsg.setKeyVersion(ctx.getKeyVersion()); + defMsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); + defMsg.setSecretUuid(ctx.getSourceSecretUuid()); + defMsg.setDescription(String.format("Define secret for VM %s before live migration", ctx.getVmUuid())); + bus.makeTargetServiceIdByResourceUuid(defMsg, HostConstant.SERVICE_ID, defMsg.getHostUuid()); + MessageReply defReply = bus.call(defMsg); + if (!defReply.isSuccess()) { + throw new OperationFailureException(defReply.getError()); + } + } + @Override public void afterMigrateVm(VmInstanceInventory inv, String srcHostUuid, NoErrorCompletion completion) { String vmUuid = inv == null ? null : inv.getUuid(); @@ -717,6 +779,7 @@ private void deleteHostSecretBestEffort(String hostUuid, String vmUuid, Integer dmsg.setVmUuid(vmUuid); dmsg.setPurpose("vtpm"); dmsg.setKeyVersion(keyVersion); + dmsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); bus.makeTargetServiceIdByResourceUuid(dmsg, HostConstant.SERVICE_ID, hostUuid); bus.send(dmsg, new CloudBusCallBack(null) { @Override diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmManager.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmManager.java index 7b8da96fc97..6152dff890a 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmManager.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmManager.java @@ -422,6 +422,7 @@ public void done(ErrorCodeList errorCodeList) { dmsg.setVmUuid(context.vmInstanceUuid); dmsg.setPurpose("vtpm"); dmsg.setKeyVersion(context.keyVersion); + dmsg.setUsageInstance(KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM); bus.makeTargetServiceIdByResourceUuid(dmsg, HostConstant.SERVICE_ID, hostUuid); bus.send(dmsg, new CloudBusCallBack(whileCompletion) { @Override @@ -812,6 +813,7 @@ protected void scripts() { dmsg.setVmUuid(vmUuid); dmsg.setPurpose("vtpm"); dmsg.setKeyVersion(context.keyVersion); + dmsg.setUsageInstance(KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM); bus.makeTargetServiceIdByResourceUuid(dmsg, HostConstant.SERVICE_ID, hostUuid); bus.send(dmsg, new CloudBusCallBack(whileCompletion) { @Override diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/VtpmMigratePreAgentContext.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/VtpmMigratePreAgentContext.java new file mode 100644 index 00000000000..06d7b7c6784 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/VtpmMigratePreAgentContext.java @@ -0,0 +1,102 @@ +package org.zstack.kvm.tpm; + +import org.zstack.header.keyprovider.EncryptedResourceKeyManager.ResourceKeyResult; + +import java.util.Objects; + +/** + * Context for vTPM secret preparation on the source KVM host immediately before the agent + * {@code migrate-vm} call. + *

+ * Built during {@link org.zstack.header.vm.VmInstanceMigrateExtensionPoint#preMigrateVm} (e.g. in + * {@link KvmTpmExtensions}) to hold VM/source/destination hosts and resolved TPM/key/secret fields. + * This replaces the previous split between a thin migrate wrapper + * and a separate object meant for FlowChain {@code Map} storage. + */ +public final class VtpmMigratePreAgentContext { + private final String vmUuid; + private final String srcHostUuid; + private final String dstHostUuid; + private boolean enableKeyProvider = true; + + private String tpmUuid; + private String providerUuid; + private String providerName; + private Integer keyVersion; + private ResourceKeyResult resourceKeyResult; + private String sourceSecretUuid; + + public VtpmMigratePreAgentContext(String vmUuid, String srcHostUuid, String dstHostUuid) { + this.vmUuid = Objects.requireNonNull(vmUuid, "vmUuid"); + this.srcHostUuid = Objects.requireNonNull(srcHostUuid, "srcHostUuid"); + this.dstHostUuid = Objects.requireNonNull(dstHostUuid, "dstHostUuid"); + } + + public String getVmUuid() { + return vmUuid; + } + + public String getSrcHostUuid() { + return srcHostUuid; + } + + public String getDstHostUuid() { + return dstHostUuid; + } + + public boolean isEnableKeyProvider() { + return enableKeyProvider; + } + + public void setEnableKeyProvider(boolean enableKeyProvider) { + this.enableKeyProvider = enableKeyProvider; + } + + public String getTpmUuid() { + return tpmUuid; + } + + public void setTpmUuid(String tpmUuid) { + this.tpmUuid = tpmUuid; + } + + public String getProviderUuid() { + return providerUuid; + } + + public void setProviderUuid(String providerUuid) { + this.providerUuid = providerUuid; + } + + public String getProviderName() { + return providerName; + } + + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public ResourceKeyResult getResourceKeyResult() { + return resourceKeyResult; + } + + public void setResourceKeyResult(ResourceKeyResult resourceKeyResult) { + this.resourceKeyResult = resourceKeyResult; + } + + public String getSourceSecretUuid() { + return sourceSecretUuid; + } + + public void setSourceSecretUuid(String sourceSecretUuid) { + this.sourceSecretUuid = sourceSecretUuid; + } +}