diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index d904e9a975bb..f494a13d87df 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -51,6 +51,7 @@ import javax.naming.ConfigurationException; import javax.xml.parsers.ParserConfigurationException; +import com.xensource.xenapi.VTPM; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageAnswer; import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand; @@ -5826,4 +5827,82 @@ public void destroyVm(VM vm, Connection connection, boolean forced) throws XenAP public void destroyVm(VM vm, Connection connection) throws XenAPIException, XmlRpcException { destroyVm(vm, connection, false); } + + /** + * Configure vTPM (Virtual Trusted Platform Module) support for a VM. + * vTPM provides a virtual TPM 2.0 device for VMs, enabling features like Secure Boot and disk encryption. + * + * Requirements: + * - XenServer/XCP-ng 8.3 (and above) + * - UEFI Secure Boot enabled + * - VM in halted state + * + * @param conn XenServer connection + * @param vm The VM to configure + * @param vmSpec VM specification containing vTPM settings + */ + public void configureVTPM(Connection conn, VM vm, VirtualMachineTO vmSpec) throws XenAPIException, XmlRpcException { + if (vmSpec == null || vmSpec.getDetails() == null) { + return; + } + + String vtpmEnabled = vmSpec.getDetails().getOrDefault(VmDetailConstants.VIRTUAL_TPM_ENABLED, null); + + final Map platform = vm.getPlatform(conn); + if (platform != null) { + final String guestRequiresVtpm = platform.get("vtpm"); + if (guestRequiresVtpm != null && Boolean.parseBoolean(guestRequiresVtpm) && !Boolean.parseBoolean(vtpmEnabled)) { + logger.warn("Guest OS requires vTPM by default, even if VM details doesn't have the setting: {}", vmSpec.getName()); + return; + } + } + + if (!Boolean.parseBoolean(vtpmEnabled)) { + return; + } + + String bootMode = StringUtils.defaultIfEmpty(vmSpec.getDetails().get(ApiConstants.BootType.UEFI.toString()), null); + String bootType = (bootMode == null) ? ApiConstants.BootType.BIOS.toString() : ApiConstants.BootType.UEFI.toString(); + + if (!ApiConstants.BootType.UEFI.toString().equals(bootType)) { + logger.warn("vTPM requires UEFI boot mode. Skipping vTPM configuration for VM: {}", vmSpec.getName()); + return; + } + + try { + Set existingVtpms = vm.getVTPMs(conn); + if (!existingVtpms.isEmpty()) { + logger.debug("vTPM already exists for VM: {}", vmSpec.getName()); + return; + } + + // Creates vTPM using: xe vtpm-create vm-uuid= + String vmUuid = vm.getUuid(conn); + String result = callHostPlugin(conn, "vmops", "create_vtpm", "vm_uuid", vmUuid); + + if (result == null || result.isEmpty() || result.startsWith("ERROR:") || result.startsWith("EXCEPTION:")) { + throw new CloudRuntimeException("Failed to create vTPM, result: " + result); + } + + logger.info("Successfully created vTPM {} for VM: {}", result.trim(), vmSpec.getName()); + } catch (Exception e) { + logger.warn("Failed to configure vTPM for VM: {}, continuing without vTPM", vmSpec.getName(), e); + } + } + + public boolean isVTPMSupported(Connection conn, Host host) { + try { + Host.Record hostRecord = host.getRecord(conn); + String productVersion = hostRecord.softwareVersion.get("product_version"); + if (productVersion == null) { + return false; + } + ComparableVersion currentVersion = new ComparableVersion(productVersion); + ComparableVersion minVersion = new ComparableVersion("8.2.0"); + return currentVersion.compareTo(minVersion) >= 0; + } catch (Exception e) { + logger.warn("Failed to check vTPM support on host", e); + return false; + } + } } diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStartCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStartCommandWrapper.java index 155cf983548b..327a11a6ac9b 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStartCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStartCommandWrapper.java @@ -97,6 +97,14 @@ public Answer execute(final StartCommand command, final CitrixResourceBase citri citrixResourceBase.createVGPU(conn, command, vm, gpuDevice); } + try { + if (citrixResourceBase.isVTPMSupported(conn, host)) { + citrixResourceBase.configureVTPM(conn, vm, vmSpec); + } + } catch (Exception e) { + logger.warn("Failed to configure vTPM for VM " + vmName + ", continuing without vTPM", e); + } + Host.Record record = host.getRecord(conn); String xenBrand = record.softwareVersion.get("product_brand"); String xenVersion = record.softwareVersion.get("product_version"); diff --git a/scripts/vm/hypervisor/xenserver/xenserver84/vmops b/scripts/vm/hypervisor/xenserver/xenserver84/vmops index cf6e6325d688..76d4571ef181 100755 --- a/scripts/vm/hypervisor/xenserver/xenserver84/vmops +++ b/scripts/vm/hypervisor/xenserver/xenserver84/vmops @@ -1587,6 +1587,43 @@ def network_rules(session, args): except: logging.exception("Failed to network rule!") +@echo +def create_vtpm(session, args): + util.SMlog("create_vtpm called with args: %s" % str(args)) + + try: + vm_uuid = args.get('vm_uuid') + if not vm_uuid: + return "ERROR: vm_uuid parameter is required" + + # Check if vTPM already exists for this VM + cmd = ['xe', 'vtpm-list', 'vm-uuid=' + vm_uuid, '--minimal'] + result = util.pread2(cmd) + existing_vtpms = result.strip() + + if existing_vtpms: + util.SMlog("vTPM already exists for VM %s: %s" % (vm_uuid, existing_vtpms)) + return existing_vtpms.split(',')[0] + + cmd = ['xe', 'vtpm-create', 'vm-uuid=' + vm_uuid] + result = util.pread2(cmd) + vtpm_uuid = result.strip() + + if vtpm_uuid: + util.SMlog("Successfully created vTPM %s for VM %s" % (vtpm_uuid, vm_uuid)) + return vtpm_uuid + else: + return "ERROR: Failed to create vTPM, empty result" + + except CommandException as e: + error_msg = "xe command failed: %s" % str(e) + util.SMlog("ERROR: %s" % error_msg) + return "ERROR: " + error_msg + except Exception as e: + error_msg = str(e) + util.SMlog("ERROR: %s" % error_msg) + return "ERROR: " + error_msg + if __name__ == "__main__": XenAPIPlugin.dispatch({"pingtest": pingtest, "setup_iscsi":setup_iscsi, "preparemigration": preparemigration, @@ -1604,4 +1641,5 @@ if __name__ == "__main__": "createFileInDomr":createFileInDomr, "kill_copy_process":kill_copy_process, "secureCopyToHost":secureCopyToHost, - "runPatchScriptInDomr": runPatchScriptInDomr}) + "runPatchScriptInDomr": runPatchScriptInDomr, + "create_vtpm": create_vtpm}) diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 4b469abe1fc2..02acb686f63b 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -5400,6 +5400,10 @@ private void fillVMOrTemplateDetailOptions(final Map> optio options.put(VmDetailConstants.RAM_RESERVATION, Collections.emptyList()); options.put(VmDetailConstants.VIRTUAL_TPM_ENABLED, Arrays.asList("true", "false")); } + + if (HypervisorType.XenServer.equals(hypervisorType)) { + options.put(VmDetailConstants.VIRTUAL_TPM_ENABLED, Arrays.asList("true", "false")); + } } @Override