Skip to content

Commit

Permalink
propagate windows license to the VMs
Browse files Browse the repository at this point in the history
Signed-off-by: Shahriyar Jalayeri <[email protected]>
  • Loading branch information
shjala committed Feb 12, 2025
1 parent bfca0fc commit d01e127
Show file tree
Hide file tree
Showing 14 changed files with 293 additions and 30 deletions.
24 changes: 24 additions & 0 deletions pkg/pillar/cmd/domainmgr/domainmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,18 @@ func maybeRetryBoot(ctx *domainContext, status *types.DomainStatus) {
}
defer file.Close()

// setup Windows OEM license key if enabled
if config.OemWinLicenseKey {
err = hyper.Task(status).WindowsOemLicenseKeySetup(&config.WindowsOemLicenseKey)
if err != nil {
// let the VM to boot and just log the error? or terminate?
log.Errorf("Failed to setup Windows OEM license key for %s: %s", status.DomainName, err)
status.PropagateWindowsLicenseKey = false
} else {
status.PropagateWindowsLicenseKey = true
}
}

wp := &types.WatchdogParam{Ps: ctx.ps, AgentName: agentName, WarnTime: warningTime, ErrTime: errorTime}
err = hyper.Task(status).VirtualTPMSetup(status.DomainName, wp)
if err == nil {
Expand Down Expand Up @@ -1695,6 +1707,18 @@ func doActivate(ctx *domainContext, config types.DomainConfig,
}
defer file.Close()

// setup Windows OEM license key if enabled
if config.OemWinLicenseKey {
err = hyper.Task(status).WindowsOemLicenseKeySetup(&config.WindowsOemLicenseKey)
if err != nil {
// let the VM to boot and just log the error? or terminate?
log.Errorf("Failed to setup Windows OEM license key for %s: %s", status.DomainName, err)
status.PropagateWindowsLicenseKey = false
} else {
status.PropagateWindowsLicenseKey = true
}
}

wp := &types.WatchdogParam{Ps: ctx.ps, AgentName: agentName, WarnTime: warningTime, ErrTime: errorTime}
err = hyper.Task(status).VirtualTPMSetup(status.DomainName, wp)
if err == nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/pillar/cmd/zedagent/parseconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ func parseAppInstanceConfig(getconfigCtx *getconfigContext,
appInstance.Service = cfgApp.Service
appInstance.CloudInitVersion = cfgApp.CloudInitVersion
appInstance.FixedResources.CPUsPinned = cfgApp.Fixedresources.PinCpu
appInstance.FixedResources.OemWinLicenseKey = cfgApp.OemWinLicenseKey

// Parse the snapshot related fields
if cfgApp.Snapshot != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/pillar/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ require (
github.com/jaypipes/ghw v0.8.0
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.5.0
github.com/lf-edge/edge-containers v0.0.0-20240207093504-5dfda0619b80
github.com/lf-edge/eve-api/go v0.0.0-20250204190553-54ee503d1433
github.com/lf-edge/eve-api/go v0.0.0-20250211142724-fb2a525d0a32
github.com/lf-edge/eve-libs v0.0.0-20241210085709-fc89dcac7f3c
github.com/lf-edge/eve/pkg/kube/cnirpc v0.0.0-20240315102754-0f6d1f182e0d
github.com/lf-edge/go-qemu v0.0.0-20231121152149-4c467eda0c56
Expand Down
2 changes: 2 additions & 0 deletions pkg/pillar/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1456,6 +1456,8 @@ github.com/lf-edge/eve-api/go v0.0.0-20250102213900-786246223024 h1:C+Xj3QOl+RMO
github.com/lf-edge/eve-api/go v0.0.0-20250102213900-786246223024/go.mod h1:ot6MhAhBXapUDl/hXklaX4kY88T3uC4PTg0D2wD8DzA=
github.com/lf-edge/eve-api/go v0.0.0-20250204190553-54ee503d1433 h1:GqgH3pRJKDgnpTVq2vIS2xz4lQVn2jFK/CzSKCBdQGM=
github.com/lf-edge/eve-api/go v0.0.0-20250204190553-54ee503d1433/go.mod h1:ot6MhAhBXapUDl/hXklaX4kY88T3uC4PTg0D2wD8DzA=
github.com/lf-edge/eve-api/go v0.0.0-20250211142724-fb2a525d0a32 h1:2w6XKQ81SyffTaNN9WLrXpcDoA4mU+ExEmjzeUH5Bgk=
github.com/lf-edge/eve-api/go v0.0.0-20250211142724-fb2a525d0a32/go.mod h1:ot6MhAhBXapUDl/hXklaX4kY88T3uC4PTg0D2wD8DzA=
github.com/lf-edge/eve-libs v0.0.0-20241210085709-fc89dcac7f3c h1:PN0cNV+Rwq6T358PYyuaFr3MU9xjUCPTHtUwepMACac=
github.com/lf-edge/eve-libs v0.0.0-20241210085709-fc89dcac7f3c/go.mod h1:32koNJxwKDrVL7rBLy35QzjIuMvGy6+BmLjf8Y38MQU=
github.com/lf-edge/eve/pkg/kube/cnirpc v0.0.0-20240315102754-0f6d1f182e0d h1:tUBb9M6u42LXwHAYHyh22wJeUUQlTpDkXwRXalpRqbo=
Expand Down
4 changes: 4 additions & 0 deletions pkg/pillar/hypervisor/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,7 @@ func (ctx ctrdContext) VirtualTPMTerminate(domainName string, wp *types.Watchdog
func (ctx ctrdContext) VirtualTPMTeardown(domainName string, wp *types.WatchdogParam) error {
return fmt.Errorf("not implemented")
}

func (ctx ctrdContext) WindowsOemLicenseKeySetup(wlk *types.WindowsOemLicenseKey) error {
return fmt.Errorf("not implemented")
}
4 changes: 4 additions & 0 deletions pkg/pillar/hypervisor/kubevirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,10 @@ func (ctx kubevirtContext) VirtualTPMTeardown(domainName string, wp *types.Watch
return fmt.Errorf("not implemented")
}

func (ctx kubevirtContext) WindowsOemLicenseKeySetup(wlk *types.WindowsOemLicenseKey) error {
return fmt.Errorf("not implemented")
}

// save the node-name to context map for later retrieval
func saveMyNodeUUID(ctx *kubevirtContext, nodeName string) {
if len(ctx.nodeNameMap) == 0 {
Expand Down
31 changes: 31 additions & 0 deletions pkg/pillar/hypervisor/kvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,15 @@ func (ctx KvmContext) Setup(status types.DomainStatus, config types.DomainConfig
dmArgs = append(dmArgs, "-smbios", "type=1,product=OpenStack Compute")
}

if status.PropagateWindowsLicenseKey {
if config.WindowsOemLicenseKey.Qemu.DomainArguments != nil {
dmArgs = append(dmArgs, config.WindowsOemLicenseKey.Qemu.DomainArguments...)
} else {
// this should never happen, but just in case.
return logError("Windows OEM license key is enabled but no domain arguments are provided")
}
}

os.MkdirAll(kvmStateDir+domainName, 0777)

args := []string{ctx.dmExec}
Expand Down Expand Up @@ -1517,6 +1526,9 @@ func (ctx KvmContext) CreateDomConfig(domainName string,
isVncShimVMEnabled(globalConfig, config)
qemuConfContext.DomainConfig.DisplayName = domainName

// signal Qemu to inject MS Licences via custom ACPI tables
qemuConfContext.DomainConfig.OemWinLicenseKey = config.OemWinLicenseKey

// render global device model settings
if err := tQemuGlobalConf.Execute(file, qemuConfContext); err != nil {
return logError("can't write to config file %s (%v)", file.Name(), err)
Expand Down Expand Up @@ -2007,3 +2019,22 @@ func requestvTPMTermination(id uuid.UUID, wp *types.WatchdogParam) error {

return nil
}

// WindowsOemLicenseKeySetup prepares the domain to recive Windows OEM license keys

Check failure on line 2023 in pkg/pillar/hypervisor/kvm.go

View workflow job for this annotation

GitHub Actions / yetus

codespell: recive ==> receive
func (ctx KvmContext) WindowsOemLicenseKeySetup(wlk *types.WindowsOemLicenseKey) error {
licences, err := dumpWindowsLicenceFromACPI()
if err != nil {
return logError("failed to dump Windows licence from ACPI: %v", err)
} else {
for _, licence := range licences {
licenceFile := fmt.Sprintf("file=%s", licence)
wlk.Qemu.DomainArguments = append(wlk.Qemu.DomainArguments, "-acpitable", licenceFile)
}

// add sysinfo
smbiosSysInfo := generateDmidecodeSmbiosString()
wlk.Qemu.DomainArguments = append(wlk.Qemu.DomainArguments, "-smbios", smbiosSysInfo)
}

return nil
}
4 changes: 4 additions & 0 deletions pkg/pillar/hypervisor/null.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,7 @@ func (ctx nullContext) VirtualTPMTerminate(domainName string, wp *types.Watchdog
func (ctx nullContext) VirtualTPMTeardown(domainName string, wp *types.WatchdogParam) error {
return fmt.Errorf("not implemented")
}

func (ctx nullContext) WindowsOemLicenseKeySetup(wlk *types.WindowsOemLicenseKey) error {
return fmt.Errorf("not implemented")
}
155 changes: 155 additions & 0 deletions pkg/pillar/hypervisor/win.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright (c) 2025 Zededa, Inc.
// SPDX-License-Identifier: Apache-2.0

package hypervisor

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

uuid "github.com/satori/go.uuid"

"github.com/lf-edge/eve/pkg/pillar/types"
fileutils "github.com/lf-edge/eve/pkg/pillar/utils/file"
"github.com/sirupsen/logrus"
)

// TODO: move all the windows related OVMF code to this file

// Struct to hold system information

Check failure on line 22 in pkg/pillar/hypervisor/win.go

View workflow job for this annotation

GitHub Actions / yetus

revive: comment on exported type DmiSystemInfo should be of the form "DmiSystemInfo ..." (with optional leading article) https://revive.run/r#exported
type DmiSystemInfo struct {
Manufacturer string
ProductName string
Version string
SerialNumber string
UUID string
SKUNumber string
Family string
}

// getSmbiosString returns a SMBIOS type 1 string (system information)
func getSmbiosString(sysInfo DmiSystemInfo) string {
var fields []string

if sysInfo.UUID != "" {
fields = append(fields, fmt.Sprintf("uuid=%s", sysInfo.UUID))
}
if sysInfo.Manufacturer != "" {
fields = append(fields, fmt.Sprintf("manufacturer=%s", sysInfo.Manufacturer))
}
if sysInfo.ProductName != "" {
fields = append(fields, fmt.Sprintf("product=%s", sysInfo.ProductName))
}
if sysInfo.Version != "" {
fields = append(fields, fmt.Sprintf("version=%s", sysInfo.Version))
}
if sysInfo.SerialNumber != "" {
fields = append(fields, fmt.Sprintf("serial=%s", sysInfo.SerialNumber))
}
if sysInfo.SKUNumber != "" {
fields = append(fields, fmt.Sprintf("sku=%s", sysInfo.SKUNumber))
}
if sysInfo.Family != "" {
fields = append(fields, fmt.Sprintf("family=%s", sysInfo.Family))
}

smbiosString := "type=1," + strings.Join(fields, ",")
return smbiosString
}

// generateDmidecodeSmbiosString execute dmidecode and creates the SMBIOS string
func generateDmidecodeSmbiosString() string {
validate := func(a string) string {
a = strings.TrimSpace(a)
if strings.Contains(a, ",") {
logrus.Warnf("Invalid value: %s", a)
return ""
}
return a
}

dmidecode := func(arg string) string {
cmd := exec.Command("dmidecode", "-s", arg)
output, err := cmd.CombinedOutput()
if err != nil {
logrus.Warnf("Failed to run dmidecode %s : %v", arg, err)
return ""
}

return validate(string(output))
}

sysInfo := DmiSystemInfo{}
sysInfo.Manufacturer = validate(dmidecode("system-manufacturer"))
sysInfo.ProductName = validate(dmidecode("system-product-name"))
sysInfo.Version = validate(dmidecode("system-version"))
sysInfo.SerialNumber = validate(dmidecode("system-serial-number"))
sysInfo.SKUNumber = validate(dmidecode("system-sku-number"))
sysInfo.Family = validate(dmidecode("system-family"))
smbiosString := getSmbiosString(sysInfo)
sysInfo.UUID = validate(dmidecode("system-uuid"))
if sysInfo.UUID != "" {
_, err := uuid.FromString(sysInfo.UUID)
if err != nil {
logrus.Warnf("Invalid UUID: %s", sysInfo.UUID)
sysInfo.UUID = ""
}
}

logrus.Infof("Generated SMBIOS string: %s", smbiosString)
return smbiosString
}

// dumpWindowsLicenceFromACPI dumps the Windows licence from ACPI tables
func dumpWindowsLicenceFromACPI() ([]string, error) {
if _, err := os.Stat(types.MsWindowsLicenceStore); os.IsNotExist(err) {
if err := os.MkdirAll(types.MsWindowsLicenceStore, 0600); err != nil {
return nil, fmt.Errorf("failed to create directory %s: %v", types.MsWindowsLicenceStore, err)
}
} else {
return nil, fmt.Errorf("erro while checking directory %s: %v", types.MsWindowsLicenceStore, err)

Check failure on line 113 in pkg/pillar/hypervisor/win.go

View workflow job for this annotation

GitHub Actions / yetus

codespell: erro ==> error
}

collectedLicences := []string{}
// TODO: testing, revert this back
//acpiTablePath := "/sys/firmware/acpi/tables"
acpiTablePath := "/hostfs/etc/winlic"
windowsLicenceTables := map[string]bool{
"MSDM": true,
"SLIC": false,
}

for table, mandatory := range windowsLicenceTables {
winLicStorePath := filepath.Join(types.MsWindowsLicenceStore, table)
// skip if we already have the table dumped
if _, err := os.Stat(winLicStorePath); err == nil {
continue
}

sysFsPath := filepath.Join(acpiTablePath, table)
if _, err := os.Stat(sysFsPath); err != nil {
if mandatory {
return nil, fmt.Errorf("error while checking mandatory %s table : %w", table, err)
} else {
logrus.Warnf("error while checkin %s table : %v", table, err)
continue
}
}

if err := fileutils.CopyFile(sysFsPath, winLicStorePath); err != nil {
if mandatory {
return nil, fmt.Errorf("failed to copy %s table: %w", table, err)
} else {
logrus.Warnf("failed to copy %s table: %v", table, err)
continue
}
}

collectedLicences = append(collectedLicences, winLicStorePath)
}

return collectedLicences, nil
}
4 changes: 4 additions & 0 deletions pkg/pillar/hypervisor/xen.go
Original file line number Diff line number Diff line change
Expand Up @@ -896,3 +896,7 @@ func (ctx xenContext) VirtualTPMTerminate(domainName string, wp *types.WatchdogP
func (ctx xenContext) VirtualTPMTeardown(domainName string, wp *types.WatchdogParam) error {
return fmt.Errorf("not implemented")
}

func (ctx xenContext) WindowsOemLicenseKeySetup(wlk *types.WindowsOemLicenseKey) error {
return fmt.Errorf("not implemented")
}
20 changes: 20 additions & 0 deletions pkg/pillar/types/domainmgrtypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ type DomainConfig struct {
// once the version is changed cloud-init tool restarts in a guest.
// See getCloudInitVersion() and createCloudInitISO() for details.
CloudInitVersion uint32

// WindowsOemLicenseKey provides the information required to propogate the OEM license key

Check failure on line 66 in pkg/pillar/types/domainmgrtypes.go

View workflow job for this annotation

GitHub Actions / yetus

codespell: propogate ==> propagate
// to the VM.
WindowsOemLicenseKey WindowsOemLicenseKey
}

// MetaDataType of metadata service for app
Expand Down Expand Up @@ -263,6 +267,18 @@ type VmConfig struct {
EnableVncShimVM bool
// Enables enforcement of user-defined ordering for network interfaces.
EnforceNetworkInterfaceOrder bool
// OemWinLicenseKey indicates the app should receive the embedded Windows license key (if available)
OemWinLicenseKey bool
}

// WindowsOemLicenseKey contains the infromation required to propogate the OEM license key

Check failure on line 274 in pkg/pillar/types/domainmgrtypes.go

View workflow job for this annotation

GitHub Actions / yetus

codespell: infromation ==> information

Check failure on line 274 in pkg/pillar/types/domainmgrtypes.go

View workflow job for this annotation

GitHub Actions / yetus

codespell: propogate ==> propagate
// to the VM. Currently only QEMU/KVM is supported.
type WindowsOemLicenseKey struct {
Qemu struct {
DomainArguments []string
}
Xen struct{}
Acrn struct{}
}

// VmMode is the type for the virtualization mode
Expand All @@ -284,6 +300,7 @@ type Task interface {
VirtualTPMSetup(domainName string, wp *WatchdogParam) error
VirtualTPMTerminate(domainName string, wp *WatchdogParam) error
VirtualTPMTeardown(domainName string, wp *WatchdogParam) error
WindowsOemLicenseKeySetup(*WindowsOemLicenseKey) error
Create(string, string, *DomainConfig) (int, error)
Start(string) error
Stop(string, bool) error
Expand Down Expand Up @@ -330,6 +347,9 @@ type DomainStatus struct {
// the device name is used for kube node name
// Need to pass in from domainmgr to hypervisor context commands
NodeName string
// PropagateWindowsLicenseKey is true if eveything it available to propogate

Check failure on line 350 in pkg/pillar/types/domainmgrtypes.go

View workflow job for this annotation

GitHub Actions / yetus

codespell: propogate ==> propagate
// the OEM license key to the VM.
PropagateWindowsLicenseKey bool
}

func (status DomainStatus) Key() string {
Expand Down
2 changes: 2 additions & 0 deletions pkg/pillar/types/locationconsts.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const (
PersistCachePatchEnvelopes = PersistDir + "/patchEnvelopesCache"
// PersistCachePatchEnvelopesUsage - folder to store patch envelopes usage stat per app
PersistCachePatchEnvelopesUsage = PersistDir + "/patchEnvelopesUsageCache"
// MsWindowsLicenceStore - Location to storeing MS Windows Licences dumped from ACPI tables

Check failure on line 46 in pkg/pillar/types/locationconsts.go

View workflow job for this annotation

GitHub Actions / yetus

codespell: storeing ==> storing
MsWindowsLicenceStore = PersistDir + "/msWindowsLicenceStore"

// IdentityDirname - Config dir
IdentityDirname = "/config"
Expand Down
Loading

0 comments on commit d01e127

Please sign in to comment.