fix(region,host,host-deployer): add win11 tpm support (#23952)

This commit is contained in:
wanyaoqi
2025-12-29 11:18:56 +08:00
committed by GitHub
parent f845188037
commit 7dfe635b62
13 changed files with 166 additions and 11 deletions

View File

@@ -131,6 +131,7 @@ func init() {
cmd.BatchPerform("sync-os-info", &options.ServerIdsOptions{})
cmd.BatchPerform("set-root-disk-matcher", &options.ServerSetRootDiskMatcher{})
cmd.Perform("disable-auto-merge-snapshot", &options.ServerDisableAutoMergeSnapshot{})
cmd.BatchPerform("set-tpm", &options.ServerSetTpmOptions{})
cmd.Perform("set-kickstart", &options.ServerKickstartConfigOptions{})
cmd.Perform("delete-kickstart", &options.ServerIdOptions{})
cmd.Perform("kickstart-complete", &options.ServerKickstartCompleteOptions{})

View File

@@ -566,7 +566,8 @@ type ServerCreateInput struct {
// BIOS类型, 若镜像是Windows并且支持UEFI,则自动会设置为UEFI
// emulate: BIOS, UEFI
Bios string `json:"bios"`
Bios string `json:"bios"`
EnableTpm bool `json:"enable_tpm"`
// Machine类型
// emulate: pc, q35

View File

@@ -348,6 +348,8 @@ const (
VM_METADATA_START_VCPU_COUNT = "start_vcpu_count"
VM_METADATA_DISABLE_AUTO_MERGE_SNAPSHOT = "disable_auto_merge_snapshot"
VM_METADATA_ENABLE_TPM = "enable_tpm"
VM_METADATA_RELEASED_DEVICES = "released_devices"
VM_METADATA_CPU_NUMA_PIN = "__cpu_numa_pin"

View File

@@ -6912,6 +6912,15 @@ func (self *SGuest) PerformEnableMemclean(ctx context.Context, userCred mcclient
return nil, self.SetMetadata(ctx, api.VM_METADATA_ENABLE_MEMCLEAN, "true", userCred)
}
func (self *SGuest) PerformSetTpm(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
enableTpm := jsonutils.QueryBoolean(data, api.VM_METADATA_ENABLE_TPM, false)
if enableTpm {
return nil, self.SetMetadata(ctx, api.VM_METADATA_ENABLE_TPM, enableTpm, userCred)
} else {
return nil, self.RemoveMetadata(ctx, api.VM_METADATA_ENABLE_TPM, userCred)
}
}
// 设置操作系统信息
func (self *SGuest) PerformSetOsInfo(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSetOSInfoInput) (jsonutils.JSONObject, error) {
drv, err := self.GetDriver()

View File

@@ -1812,6 +1812,17 @@ func (manager *SGuestManager) validateCreateData(
input.OsArch = apis.OS_ARCH_AARCH64
}
// enable tpm on windows 11 image
if osDist := imgProperties["os_distribution"]; strings.Contains(osDist, "Windows 11") {
input.EnableTpm = true
}
// use uefi boot and q35 machine type on enable tpm
if input.EnableTpm {
input.Bios = "UEFI"
input.Machine = api.VM_MACHINE_TYPE_Q35
}
if imageDiskFormat != "iso" {
var imgSupportUEFI *bool
var imgSupportBIOS *bool
@@ -2717,6 +2728,9 @@ func (guest *SGuest) PostCreate(ctx context.Context, userCred mcclient.TokenCred
if jsonutils.QueryBoolean(data, api.VM_METADATA_ENABLE_MEMCLEAN, false) {
guest.SetMetadata(ctx, api.VM_METADATA_ENABLE_MEMCLEAN, "true", userCred)
}
if jsonutils.QueryBoolean(data, api.VM_METADATA_ENABLE_TPM, false) {
guest.SetMetadata(ctx, api.VM_METADATA_ENABLE_TPM, "true", userCred)
}
if jsonutils.QueryBoolean(data, imageapi.IMAGE_DISABLE_USB_KBD, false) {
guest.SetMetadata(ctx, imageapi.IMAGE_DISABLE_USB_KBD, "true", userCred)
}

View File

@@ -116,6 +116,8 @@ type SGuestHardwareDesc struct {
VirtioSerial *SGuestVirtioSerial
Tpm *SGuestTpm
// std virtio cirrus vmware qlx none
Vga string
VgaDevice *SGuestVga `json:",omitempty"`
@@ -173,6 +175,13 @@ type SGuestPvpanic struct {
Id string
}
type SGuestTpm struct {
TpmSock *CharDev
// default emulator tpm
Id string
}
// -device pcie-pci-bridge,id=pci.1,bus=pcie.0 \
// -device pci-bridge,id=pci.2,bus=pci.1,chassis_nr=1,addr=0x01 \

View File

@@ -99,6 +99,7 @@ func (s *SKVMGuestInstance) initGuestDevicesDesc(pciRoot, pciBridge *desc.PCICon
s.initQgaDesc()
s.initPvpanicDesc()
s.initIsaSerialDesc()
s.initTpmDesc()
return nil
}

View File

@@ -299,6 +299,10 @@ func (s *SKVMGuestInstance) disablePvpanicDev() bool {
return s.Desc.Metadata["disable_pvpanic"] == "true"
}
func (s *SKVMGuestInstance) enableTpmDev() bool {
return s.Desc.Metadata[api.VM_METADATA_ENABLE_TPM] == "true"
}
func (s *SKVMGuestInstance) getQuorumChildIndex() int64 {
if sidx, ok := s.Desc.Metadata[api.QUORUM_CHILD_INDEX]; ok {
idx, _ := strconv.ParseInt(sidx, 10, 0)
@@ -364,6 +368,7 @@ func (s *SKVMGuestInstance) generateStartScript(data *jsonutils.JSONDict) (strin
HomeDir: s.HomeDir(),
HugepagesEnabled: s.manager.host.IsHugepagesEnabled(),
EnableMemfd: s.isMemcleanEnabled(),
EnableTpm: s.enableTpmDev(),
PidFilePath: s.GetPidFilePath(),
}
@@ -491,6 +496,28 @@ function nic_mtu() {
}
`
if input.EnableTpm {
input.OVMFPath = options.HostOptions.SecbootOvmfPath
input.OVMFVarsPath = options.HostOptions.SecbootOvmfVarsPath
cmd += `
function start_swtpm() {
local swtpm_binary=$1
local swtpm_dir=$2
local swtpm_socket=$swtpm_dir/swtpm.sock
local swtpm_log=$swtpm_dir/swtpm.log
local swtpm_pid=$swtpm_dir/swtpm.pid
if [ -f "$swtpm_pid" ] && ps -p $(cat "$swtpm_pid") >/dev/null 2>&1; then
return 0
fi
mkdir -p $swtpm_dir
$swtpm_binary socket --tpmstate dir=$swtpm_dir --ctrl type=unixio,path=$swtpm_socket --log file=$swtpm_log,level=20 --pid file=$swtpm_pid --tpm2 -d
}
`
cmd += fmt.Sprintf("start_swtpm %s %s\n", options.HostOptions.BinarySwtpmPath, s.getSwtpmDirPath())
}
// Generate Start VM script
cmd += `CMD="$QEMU_CMD $QEMU_CMD_KVM_ARG`
@@ -935,6 +962,21 @@ func (s *SKVMGuestInstance) generateStopScript(data *jsonutils.JSONDict) string
cmd += " fi\n"
cmd += "done\n"
if s.enableTpmDev() {
// stop swtpm
cmd += fmt.Sprintf("SWTPM_PID_FILE=%s\n", s.getSwtpmPidPath())
cmd += "if [ -f $SWTPM_PID_FILE ]; then\n"
cmd += " SWTPM_PID=`cat $SWTPM_PID_FILE`\n"
cmd += " ps -p $SWTPM_PID > /dev/null\n"
cmd += " if [ $? -eq 0 ]; then\n"
cmd += " echo \"Kill swtpm process $SWTPM_PID\"\n"
cmd += " kill -9 $SWTPM_PID > /dev/null 2>&1\n"
cmd += " fi\n"
cmd += " echo \"Remove swtpm PID $SWTPM_PID_FILE\"\n"
cmd += " rm -f $SWTPM_PID_FILE\n"
cmd += "fi\n"
}
for _, nic := range nics {
if nic.Driver == api.NETWORK_DRIVER_VFIO {
dev, _ := s.GetSriovDeviceByNetworkIndex(nic.Index)
@@ -1001,6 +1043,22 @@ func (s *SKVMGuestInstance) getPKIDirPath() string {
return path.Join(s.HomeDir(), "pki")
}
func (s *SKVMGuestInstance) getSwtpmDirPath() string {
return path.Join(s.HomeDir(), "swtpm")
}
func (s *SKVMGuestInstance) getSwtpmSocketPath() string {
return path.Join(s.getSwtpmDirPath(), "swtpm.sock")
}
func (s *SKVMGuestInstance) getSwtpmLogPath() string {
return path.Join(s.getSwtpmDirPath(), "swtpm.log")
}
func (s *SKVMGuestInstance) getSwtpmPidPath() string {
return path.Join(s.getSwtpmDirPath(), "swtpm.pid")
}
func (s *SKVMGuestInstance) makePKIDir() error {
output, err := procutils.NewCommand("mkdir", "-p", s.getPKIDirPath()).Output()
if err != nil {
@@ -1312,6 +1370,19 @@ func (s *SKVMGuestInstance) initIsaSerialDesc() {
}
}
func (s *SKVMGuestInstance) initTpmDesc() {
if s.enableTpmDev() {
charDevId := "chrtpm"
s.Desc.Tpm = &desc.SGuestTpm{
TpmSock: desc.NewCharDev("socket", charDevId, ""),
Id: "tpm0",
}
s.Desc.Tpm.TpmSock.Options = map[string]string{
"path": s.getSwtpmSocketPath(),
}
}
}
func (s *SKVMGuestInstance) getVfioDeviceHotPlugPciControllerType() *desc.PCI_CONTROLLER_TYPE {
if s.Desc.Machine == api.VM_MACHINE_TYPE_Q35 || s.Desc.Machine == api.VM_MACHINE_TYPE_ARM_VIRT {
_, _, found := s.findUnusedSlotForController(desc.CONTROLLER_TYPE_PCIE_ROOT_PORT, 0)

View File

@@ -666,6 +666,14 @@ func generatePvpanicDeviceOption(pvpanic *desc.SGuestPvpanic) string {
return fmt.Sprintf("-device pvpanic,id=%s,ioport=0x%x", pvpanic.Id, pvpanic.Ioport)
}
func generateTpmDevOptions(tpm *desc.SGuestTpm) []string {
opts := make([]string, 0)
opts = append(opts, chardevOption(tpm.TpmSock))
opts = append(opts, fmt.Sprintf("-tpmdev emulator,id=%s,chardev=%s", tpm.Id, tpm.TpmSock.Id))
opts = append(opts, fmt.Sprintf("-device tpm-tis,tpmdev=%s", tpm.Id))
return opts
}
func getMigrateOptions(drvOpt QemuOptions, input *GenerateStartOptionsInput) []string {
opts := make([]string, 0)
if input.NeedMigrate {
@@ -692,6 +700,7 @@ type GenerateStartOptionsInput struct {
OsName string
HugepagesEnabled bool
EnableMemfd bool
EnableTpm bool
OVNIntegrationBridge string
Devices []string
@@ -939,6 +948,10 @@ func GenerateStartOptions(
opts = append(opts, generatePvpanicDeviceOption(input.GuestDesc.Pvpanic))
}
if input.GuestDesc.Tpm != nil {
opts = append(opts, generateTpmDevOptions(input.GuestDesc.Tpm)...)
}
// move extra options to end of cmdline
if len(input.ExtraOptions) != 0 {
opts = append(opts, input.ExtraOptions...)

View File

@@ -17,6 +17,7 @@ package qemu
import (
"fmt"
"path"
"path/filepath"
"strings"
"sync"
@@ -273,20 +274,24 @@ func (o baseOptions) Boot(order *string, enableMenu bool) string {
}
func (o baseOptions) BIOS(ovmfPath, ovmfVarsPath, homedir string) (string, error) {
if ovmfVarsPath == "" || !fileutils2.Exists(ovmfVarsPath) {
ovmfVarsPath = ovmfPath
ovmfVarsName := "OVMF_VARS.fd"
if ovmfVarsPath != "" {
ovmfVarsName = filepath.Base(ovmfVarsPath)
}
destOvmfVarsPath := path.Join(homedir, "OVMF_VARS.fd")
if !fileutils2.Exists(destOvmfVarsPath) {
err := procutils.NewRemoteCommandAsFarAsPossible("cp", "-f", ovmfVarsPath, destOvmfVarsPath).Run()
guestOvmfVarsPath := path.Join(homedir, ovmfVarsName)
if !fileutils2.Exists(guestOvmfVarsPath) {
sourceOvmfVarsPath := ovmfPath
if ovmfVarsPath != "" {
sourceOvmfVarsPath = ovmfVarsPath
}
err := procutils.NewRemoteCommandAsFarAsPossible("cp", "-f", sourceOvmfVarsPath, guestOvmfVarsPath).Run()
if err != nil {
return "", errors.Wrap(err, "failed copy ovmf vars")
}
}
return fmt.Sprintf(
"-drive if=pflash,format=raw,unit=0,file=%s,readonly=on -drive if=pflash,format=raw,unit=1,file=%s",
ovmfPath, destOvmfVarsPath,
ovmfPath, guestOvmfVarsPath,
), nil
}

View File

@@ -113,9 +113,11 @@ type SHostOptions struct {
DnsServer string `help:"Address of host DNS server"`
DnsServerLegacy string `help:"Deprecated Address of host DNS server"`
ChntpwPath string `help:"path to chntpw tool" default:"/usr/local/bin/chntpw.static"`
OvmfPath string `help:"Path to OVMF.fd" default:"/opt/cloud/contrib/OVMF.fd"`
OvmfVarsPath string `help:"Path to OVMF_VARS.fd" default:"/opt/cloud/contrib/OVMF_VARS.fd"`
ChntpwPath string `help:"path to chntpw tool" default:"/usr/local/bin/chntpw.static"`
OvmfPath string `help:"Path to OVMF.fd" default:"/opt/cloud/contrib/OVMF.fd"`
OvmfVarsPath string `help:"Path to OVMF_VARS.fd" default:"/opt/cloud/contrib/OVMF_VARS.fd"`
SecbootOvmfPath string `help:"Path to secboot ovmf fd" default:"/opt/cloud/contrib/OVMF_CODE_4M.secboot.fd"`
SecbootOvmfVarsPath string `help:"Path to secboot ovmf vars fd" default:"/opt/cloud/contrib/OVMF_VARS_4M.fd"`
LinuxDefaultRootUser bool `help:"Default account for linux system is root"`
WindowsDefaultAdminUser bool `default:"true" help:"Default account for Windows system is Administrator"`
@@ -234,6 +236,7 @@ type SHostOptions struct {
LocalBackupTempPath string `help:"the local temporary directory for backup" default:"/opt/cloud/workspace/run/backups"`
BinaryMemcleanPath string `help:"execute binary memclean path" default:"/opt/yunion/bin/memclean"`
BinarySwtpmPath string `help:"swtpm binary path" default:"/usr/bin/swtpm"`
MaxHotplugVCpuCount int `help:"maximal possible vCPU count that the platform kvm supports"`
PcieRootPortCount int `help:"pcie root port count" default:"2"`

View File

@@ -451,6 +451,7 @@ type ServerCreateOptionalOptions struct {
MemSpec string `help:"Memory size Or Instance Type" metavar:"MEMSPEC" json:"-"`
CpuSockets int `help:"Cpu sockets"`
EnableMemclean bool `help:"clean guest memory after guest exit" json:"enable_memclean"`
EnableTpm bool `help:"enable tpm device" json:"enable_tpm"`
Keypair string `help:"SSH Keypair"`
Password string `help:"Default user password"`
@@ -598,6 +599,7 @@ func (opts *ServerCreateOptionalOptions) OptionalParams() (*computeapi.ServerCre
GuestImageID: opts.GuestImageID,
Secgroups: opts.Secgroups,
EnableMemclean: opts.EnableMemclean,
EnableTpm: opts.EnableTpm,
}
params.ProjectId = opts.Project
@@ -1668,6 +1670,15 @@ func (o *ServerSetRootDiskMatcher) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(matcher), nil
}
type ServerSetTpmOptions struct {
ServerIdsOptions
EnableTpm bool `help:"Enable tpm device"`
}
func (o *ServerSetTpmOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(o), nil
}
type ServerChangeBillingTypeOptions struct {
ServerIdOptions
BillingType string `choices:"prepaid|postpaid"`

View File

@@ -21,6 +21,7 @@ import (
"io/ioutil"
"path"
"regexp"
"strconv"
"strings"
"time"
@@ -619,6 +620,20 @@ func (w *SWinRegTool) SetDnsServer(nameserver, searchlist string) {
func (w *SWinRegTool) GetProductName() string {
prodKey := `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName`
prodName := w.GetRegistry(prodKey)
if strings.Contains(prodName, "Windows 10") {
// check build number to determine whether it's actually Windows 11
buildNumStr := w.GetCurrentBuildNumber()
if buildNum, err := strconv.Atoi(strings.TrimSpace(buildNumStr)); err == nil && buildNum >= 22000 {
prodName = strings.Replace(prodName, "Windows 10", "Windows 11", 1)
}
}
return prodName
}
func (w *SWinRegTool) GetCurrentBuildNumber() string {
prodKey := `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentBuildNumber`
return w.GetRegistry(prodKey)
}