diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/DummyTpmEncryptedResourceKeyBackend.java b/compute/src/main/java/org/zstack/compute/vm/devices/DummyTpmEncryptedResourceKeyBackend.java index e221c03bb4f..4ee23a2a02d 100644 --- a/compute/src/main/java/org/zstack/compute/vm/devices/DummyTpmEncryptedResourceKeyBackend.java +++ b/compute/src/main/java/org/zstack/compute/vm/devices/DummyTpmEncryptedResourceKeyBackend.java @@ -80,4 +80,9 @@ public void cleanEncryptedResourceKey(String vmHostBackupFileUuid) { // do nothing logger.debug("ignore cleanup encrypted resource key request for VmHostBackupFileVO: " + vmHostBackupFileUuid); } + + @Override + public void cleanTpmKeyBackupEncryptedResourceKey(String tpmKeyBackupUuid) { + logger.debug("ignore cleanup encrypted resource key request for TpmKeyBackupVO: " + tpmKeyBackupUuid); + } } diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/TpmEncryptedResourceKeyBackend.java b/compute/src/main/java/org/zstack/compute/vm/devices/TpmEncryptedResourceKeyBackend.java index b5d1c766249..0db19ee4525 100644 --- a/compute/src/main/java/org/zstack/compute/vm/devices/TpmEncryptedResourceKeyBackend.java +++ b/compute/src/main/java/org/zstack/compute/vm/devices/TpmEncryptedResourceKeyBackend.java @@ -97,4 +97,9 @@ static class RestoreEncryptedResourceKeyContext { void restoreEncryptedResourceKey(RestoreEncryptedResourceKeyContext context); void cleanEncryptedResourceKey(String vmHostBackupFileUuid); + + /** + * Remove encryption key material stored for a {@link org.zstack.header.tpm.entity.TpmKeyBackupVO}. + */ + void cleanTpmKeyBackupEncryptedResourceKey(String tpmKeyBackupUuid); } diff --git a/conf/db/zsv/V5.1.0__schema.sql b/conf/db/zsv/V5.1.0__schema.sql index 243651492d9..0c2187e60bb 100644 --- a/conf/db/zsv/V5.1.0__schema.sql +++ b/conf/db/zsv/V5.1.0__schema.sql @@ -1,3 +1,10 @@ +CREATE TABLE IF NOT EXISTS `zstack`.`TpmKeyBackupVO` ( + `uuid` char(32) NOT NULL UNIQUE, + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + DELETE FROM `EncryptedResourceKeyRefVO` WHERE `resourceUuid` NOT IN (SELECT `uuid` FROM `ResourceVO`); ALTER TABLE `EncryptedResourceKeyRefVO` diff --git a/conf/persistence.xml b/conf/persistence.xml index e0b9cccdb32..97fa6edff1c 100755 --- a/conf/persistence.xml +++ b/conf/persistence.xml @@ -19,6 +19,7 @@ org.zstack.header.managementnode.ManagementNodeVO org.zstack.header.managementnode.ManagementNodeContextVO org.zstack.header.tpm.entity.TpmVO + org.zstack.header.tpm.entity.TpmKeyBackupVO org.zstack.header.vm.additions.VmHostFileVO org.zstack.header.vm.additions.VmHostBackupFileVO org.zstack.header.vm.additions.VmHostFileContentVO diff --git a/header/src/main/java/org/zstack/header/tpm/entity/TpmKeyBackupVO.java b/header/src/main/java/org/zstack/header/tpm/entity/TpmKeyBackupVO.java new file mode 100644 index 00000000000..30efe258aa4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/TpmKeyBackupVO.java @@ -0,0 +1,37 @@ +package org.zstack.header.tpm.entity; + +import org.zstack.header.vo.ResourceVO; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.sql.Timestamp; + +/** + * Internal holder for a TPM encryption resource key snapshot during snapshot-group revert. + * The key material is stored in {@code EncryptedResourceKeyRefVO} with {@code resourceUuid} = this VO's uuid. + */ +@Entity +@Table +public class TpmKeyBackupVO extends ResourceVO { + @Column + private Timestamp createDate; + @Column + private Timestamp lastOpDate; + + public Timestamp getCreateDate() { + return createDate; + } + + public void setCreateDate(Timestamp createDate) { + this.createDate = createDate; + } + + public Timestamp getLastOpDate() { + return lastOpDate; + } + + public void setLastOpDate(Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/entity/TpmKeyBackupVO_.java b/header/src/main/java/org/zstack/header/tpm/entity/TpmKeyBackupVO_.java new file mode 100644 index 00000000000..f2bdc161fcd --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/TpmKeyBackupVO_.java @@ -0,0 +1,13 @@ +package org.zstack.header.tpm.entity; + +import org.zstack.header.vo.ResourceVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(TpmKeyBackupVO.class) +public class TpmKeyBackupVO_ extends ResourceVO_ { + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/DeleteTpmKeyBackupMsg.java b/header/src/main/java/org/zstack/header/tpm/message/DeleteTpmKeyBackupMsg.java new file mode 100644 index 00000000000..dd512143235 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/DeleteTpmKeyBackupMsg.java @@ -0,0 +1,24 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.NeedReplyMessage; + +public class DeleteTpmKeyBackupMsg extends NeedReplyMessage { + private String tpmUuid; + private String tpmKeyBackupUuid; + + public String getTpmUuid() { + return tpmUuid; + } + + public void setTpmUuid(String tpmUuid) { + this.tpmUuid = tpmUuid; + } + + public String getTpmKeyBackupUuid() { + return tpmKeyBackupUuid; + } + + public void setTpmKeyBackupUuid(String tpmKeyBackupUuid) { + this.tpmKeyBackupUuid = tpmKeyBackupUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/DeleteTpmKeyBackupReply.java b/header/src/main/java/org/zstack/header/tpm/message/DeleteTpmKeyBackupReply.java new file mode 100644 index 00000000000..aa49fd35dc0 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/DeleteTpmKeyBackupReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.MessageReply; + +public class DeleteTpmKeyBackupReply extends MessageReply { +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/RestoreTpmEncryptionKeyMsg.java b/header/src/main/java/org/zstack/header/tpm/message/RestoreTpmEncryptionKeyMsg.java index 0b244be60fe..15021ac881e 100644 --- a/header/src/main/java/org/zstack/header/tpm/message/RestoreTpmEncryptionKeyMsg.java +++ b/header/src/main/java/org/zstack/header/tpm/message/RestoreTpmEncryptionKeyMsg.java @@ -5,6 +5,11 @@ public class RestoreTpmEncryptionKeyMsg extends NeedReplyMessage { private String srcResourceUuid; private String dstResourceUuid; + /** + * When true, the current encryption key on {@link #dstResourceUuid} (TPM) is copied to a + * {@link org.zstack.header.tpm.entity.TpmKeyBackupVO} before restoring from {@link #srcResourceUuid}. + */ + private boolean backupCurrentKey = true; public String getSrcResourceUuid() { return srcResourceUuid; @@ -21,4 +26,12 @@ public String getDstResourceUuid() { public void setDstResourceUuid(String dstResourceUuid) { this.dstResourceUuid = dstResourceUuid; } + + public boolean isBackupCurrentKey() { + return backupCurrentKey; + } + + public void setBackupCurrentKey(boolean backupCurrentKey) { + this.backupCurrentKey = backupCurrentKey; + } } diff --git a/header/src/main/java/org/zstack/header/tpm/message/RestoreTpmEncryptionKeyReply.java b/header/src/main/java/org/zstack/header/tpm/message/RestoreTpmEncryptionKeyReply.java index 0a6e1fcae74..9cb2d539af0 100644 --- a/header/src/main/java/org/zstack/header/tpm/message/RestoreTpmEncryptionKeyReply.java +++ b/header/src/main/java/org/zstack/header/tpm/message/RestoreTpmEncryptionKeyReply.java @@ -3,4 +3,13 @@ import org.zstack.header.message.MessageReply; public class RestoreTpmEncryptionKeyReply extends MessageReply { + private String tpmKeyBackupUuid; + + public String getTpmKeyBackupUuid() { + return tpmKeyBackupUuid; + } + + public void setTpmKeyBackupUuid(String tpmKeyBackupUuid) { + this.tpmKeyBackupUuid = tpmKeyBackupUuid; + } } 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 fc1dca93c8a..6ab93c7f9df 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 @@ -4,16 +4,19 @@ import org.zstack.compute.vm.VmGlobalConfig; import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend; import org.zstack.compute.vm.devices.VmTpmManager; +import org.zstack.core.Platform; import org.zstack.core.asyncbatch.While; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; import org.zstack.core.cloudbus.MessageSafe; +import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; import org.zstack.core.db.SQL; import org.zstack.core.db.SQLBatch; import org.zstack.core.thread.ChainTask; import org.zstack.core.thread.SyncTaskChain; import org.zstack.core.thread.ThreadFacade; +import org.zstack.core.timeout.TimeHelper; import org.zstack.core.workflow.SimpleFlowChain; import org.zstack.header.AbstractService; import org.zstack.header.core.Completion; @@ -36,10 +39,13 @@ import org.zstack.header.tpm.api.APIUpdateTpmMsg; import org.zstack.header.tpm.entity.TpmCapabilityView; import org.zstack.header.tpm.entity.TpmInventory; +import org.zstack.header.tpm.entity.TpmKeyBackupVO; import org.zstack.header.tpm.entity.TpmVO; import org.zstack.header.tpm.entity.TpmVO_; import org.zstack.header.tpm.message.AddTpmMsg; import org.zstack.header.tpm.message.AddTpmReply; +import org.zstack.header.tpm.message.DeleteTpmKeyBackupMsg; +import org.zstack.header.tpm.message.DeleteTpmKeyBackupReply; import org.zstack.header.tpm.message.RestoreTpmEncryptionKeyMsg; import org.zstack.header.tpm.message.RestoreTpmEncryptionKeyReply; import org.zstack.header.tpm.message.TpmDeletionMsg; @@ -74,6 +80,7 @@ import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashSet; import java.util.HashMap; @@ -113,6 +120,10 @@ public class KvmTpmManager extends AbstractService { private EncryptedResourceKeyManager resourceKeyManager; @Autowired private KvmSecureBootExtensions secureBootExtensions; + @Autowired + private DatabaseFacade databaseFacade; + @Autowired + private TimeHelper timeHelper; @Override public boolean start() { @@ -153,6 +164,8 @@ private void handleLocalMessage(Message msg) { handle((BackupTpmEncryptionKeyMsg) msg); } else if (msg instanceof RestoreTpmEncryptionKeyMsg) { handle((RestoreTpmEncryptionKeyMsg) msg); + } else if (msg instanceof DeleteTpmKeyBackupMsg) { + handle((DeleteTpmKeyBackupMsg) msg); } else if (msg instanceof ResetVmTpmMsg) { handle((ResetVmTpmMsg) msg); } else { @@ -648,11 +661,47 @@ private void handle(BackupTpmEncryptionKeyMsg msg) { } private void handle(RestoreTpmEncryptionKeyMsg msg) { - RestoreEncryptedResourceKeyContext content = new RestoreEncryptedResourceKeyContext(); - content.srcResourceUuid = msg.getSrcResourceUuid(); - content.dstResourceUuid = msg.getDstResourceUuid(); - tpmKeyBackend.restoreEncryptedResourceKey(content); - bus.reply(msg, new RestoreTpmEncryptionKeyReply()); + RestoreTpmEncryptionKeyReply reply = new RestoreTpmEncryptionKeyReply(); + String tpmKeyBackupUuid = null; + try { + if (msg.isBackupCurrentKey() + && msg.getDstResourceUuid() != null + && tpmKeyBackend.checkTpmKeyProviderAttached(msg.getDstResourceUuid())) { + tpmKeyBackupUuid = Platform.getUuid(); + TpmKeyBackupVO backupVo = new TpmKeyBackupVO(); + backupVo.setUuid(tpmKeyBackupUuid); + Timestamp now = new Timestamp(timeHelper.getCurrentTimeMillis()); + backupVo.setCreateDate(now); + backupVo.setLastOpDate(now); + databaseFacade.persist(backupVo); + BackupEncryptedResourceKeyContext backupCtx = new BackupEncryptedResourceKeyContext(); + backupCtx.srcResourceUuid = msg.getDstResourceUuid(); + backupCtx.dstResourceUuid = tpmKeyBackupUuid; + tpmKeyBackend.backupEncryptedResourceKey(backupCtx); + reply.setTpmKeyBackupUuid(tpmKeyBackupUuid); + } + + RestoreEncryptedResourceKeyContext content = new RestoreEncryptedResourceKeyContext(); + content.srcResourceUuid = msg.getSrcResourceUuid(); + content.dstResourceUuid = msg.getDstResourceUuid(); + tpmKeyBackend.restoreEncryptedResourceKey(content); + bus.reply(msg, reply); + } catch (Exception t) { + if (tpmKeyBackupUuid != null) { + tpmKeyBackend.cleanTpmKeyBackupEncryptedResourceKey(tpmKeyBackupUuid); + databaseFacade.removeByPrimaryKey(tpmKeyBackupUuid, TpmKeyBackupVO.class); + } + throw t; + } + } + + private void handle(DeleteTpmKeyBackupMsg msg) { + DeleteTpmKeyBackupReply reply = new DeleteTpmKeyBackupReply(); + if (msg.getTpmKeyBackupUuid() != null) { + tpmKeyBackend.cleanTpmKeyBackupEncryptedResourceKey(msg.getTpmKeyBackupUuid()); + databaseFacade.removeByPrimaryKey(msg.getTpmKeyBackupUuid(), TpmKeyBackupVO.class); + } + bus.reply(msg, reply); } static class ResetVmTpmContext { diff --git a/storage/src/main/java/org/zstack/storage/snapshot/group/VolumeSnapshotGroupBase.java b/storage/src/main/java/org/zstack/storage/snapshot/group/VolumeSnapshotGroupBase.java index b99849cdb7a..fcb6907ca9e 100644 --- a/storage/src/main/java/org/zstack/storage/snapshot/group/VolumeSnapshotGroupBase.java +++ b/storage/src/main/java/org/zstack/storage/snapshot/group/VolumeSnapshotGroupBase.java @@ -32,7 +32,9 @@ import org.zstack.header.tpm.entity.TpmVO; import org.zstack.header.tpm.entity.TpmVO_; import org.zstack.header.tpm.message.AddTpmMsg; +import org.zstack.header.tpm.message.DeleteTpmKeyBackupMsg; import org.zstack.header.tpm.message.RestoreTpmEncryptionKeyMsg; +import org.zstack.header.tpm.message.RestoreTpmEncryptionKeyReply; import org.zstack.header.tpm.message.TpmDeletionMsg; import org.zstack.header.vm.additions.RestoreVmHostFileMsg; import org.zstack.header.vm.RestoreVmInstanceMsg; @@ -389,6 +391,7 @@ class Context { VolumeSnapshotGroupVO newGroup; boolean snapshotGroupHasTpm; String tpmUuid, newCreateTpmUuid; + String tpmKeyBackupUuid; } Context context = new Context(); context.snapshotGroupHasTpm = VolumeSnapshotGroupSystemTags.WITH_TPM.hasTag(msg.getUuid()); @@ -457,11 +460,15 @@ public void run(MessageReply reply) { RestoreTpmEncryptionKeyMsg restoreMsg = new RestoreTpmEncryptionKeyMsg(); restoreMsg.setSrcResourceUuid(msg.getUuid()); restoreMsg.setDstResourceUuid(context.tpmUuid); + restoreMsg.setBackupCurrentKey(true); bus.makeTargetServiceIdByResourceUuid(restoreMsg, SERVICE_ID, context.tpmUuid); bus.send(restoreMsg, new CloudBusCallBack(msg) { @Override public void run(MessageReply reply) { if (reply.isSuccess()) { + if (reply instanceof RestoreTpmEncryptionKeyReply) { + context.tpmKeyBackupUuid = ((RestoreTpmEncryptionKeyReply) reply).getTpmKeyBackupUuid(); + } logger.debug(String.format( "restore resource key of Tpm[uuid:%s] for VM[uuid:%s] for snapshotGroup[uuid:%s]", context.tpmUuid, vmUuid, msg.getUuid())); @@ -472,7 +479,43 @@ public void run(MessageReply reply) { } }); }) - // TODO: It should has rollback + .rollback(trigger -> { + if (context.tpmKeyBackupUuid == null) { + trigger.rollback(); + return; + } + RestoreTpmEncryptionKeyMsg rollbackMsg = new RestoreTpmEncryptionKeyMsg(); + rollbackMsg.setBackupCurrentKey(false); + rollbackMsg.setSrcResourceUuid(context.tpmKeyBackupUuid); + rollbackMsg.setDstResourceUuid(context.tpmUuid); + bus.makeTargetServiceIdByResourceUuid(rollbackMsg, SERVICE_ID, context.tpmUuid); + bus.send(rollbackMsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + logger.debug(String.format( + "failed to rollback TPM encryption key from TpmKeyBackupVO[uuid:%s] to Tpm[uuid:%s]", + context.tpmKeyBackupUuid, context.tpmUuid)); + } + DeleteTpmKeyBackupMsg delMsg = new DeleteTpmKeyBackupMsg(); + delMsg.setTpmUuid(context.tpmUuid); + delMsg.setTpmKeyBackupUuid(context.tpmKeyBackupUuid); + bus.makeTargetServiceIdByResourceUuid(delMsg, SERVICE_ID, context.tpmUuid); + bus.send(delMsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply2) { + if (!reply2.isSuccess()) { + logger.debug(String.format( + "failed to delete TpmKeyBackupVO[uuid:%s] after TPM key rollback", + context.tpmKeyBackupUuid)); + } + context.tpmKeyBackupUuid = null; + trigger.rollback(); + } + }); + } + }); + }) .build()) .then(Flow.of("remove-tpm-if-needed") .runIf(data -> !context.snapshotGroupHasTpm && context.tpmUuid != null) @@ -549,6 +592,26 @@ public void done(ErrorCodeList errorCodeList) { }); }) .build()) + .then(Flow.of("delete-tpm-key-backup") + .runIf(data -> context.tpmKeyBackupUuid != null) + .handle(trigger -> { + DeleteTpmKeyBackupMsg delMsg = new DeleteTpmKeyBackupMsg(); + delMsg.setTpmUuid(context.tpmUuid); + delMsg.setTpmKeyBackupUuid(context.tpmKeyBackupUuid); + bus.makeTargetServiceIdByResourceUuid(delMsg, SERVICE_ID, context.tpmUuid); + bus.send(delMsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply r) { + if (r.isSuccess()) { + context.tpmKeyBackupUuid = null; + trigger.next(); + } else { + trigger.fail(r.getError()); + } + } + }); + }) + .build()) .propagateExceptionTo(msg) .done(() -> bus.reply(msg, reply)) .error(errorCode -> {