Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execute add/remove registry entries in elevated mode with hyperv #24813

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions build_windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,9 @@ When `machine init` completes, run `machine start`:
.\bin\windows\podman.exe machine start
```

:information_source: If the virtualization provider is Hyperv-V, execute the
above commands in an administrator terminal.
:information_source: If the virtualization provider is Hyperv-V, you will be asked
to elevate privileges to add/remove specific Windows Registry Podman settings.
Additionally, you must be a member of the Hyper-V Administrators group.

### Run a container using podman

Expand Down
34 changes: 34 additions & 0 deletions pkg/machine/hyperv/hutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build windows

package hyperv

import (
"github.com/sirupsen/logrus"
"golang.org/x/sys/windows"
)

// https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
// BUILTIN\Hyper-V Administrators => S-1-5-32-578
const hypervAdminGroupSid = "S-1-5-32-578"

func HasHyperVAdminRights() bool {
sid, err := windows.StringToSid(hypervAdminGroupSid)
if err != nil {
return false
}

// From MS docs:
// "If TokenHandle is NULL, CheckTokenMembership uses the impersonation
// token of the calling thread. If the thread is not impersonating,
// the function duplicates the thread's primary token to create an
// impersonation token."
token := windows.Token(0)
member, err := token.IsMember(sid)

if err != nil {
logrus.Warnf("Token Membership Error: %s", err)
return false
}

return member
}
42 changes: 23 additions & 19 deletions pkg/machine/hyperv/stubber.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,31 @@ func (h HyperVStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineC
Memory: uint64(mc.Resources.Memory),
}

networkHVSock, err := vsock.NewHVSockRegistryEntry(mc.Name, vsock.Network)
networkHVSock, err := vsock.CreateHVSockRegistryEntry(mc.Name, vsock.Network)
if err != nil {
return err
}

mc.HyperVHypervisor.NetworkVSock = *networkHVSock

// Add vsock port numbers to mounts
err = createShares(mc)
// Create vsock port numbers to mounts
sharesVsock, err := createShares(mc)
if err != nil {
return err
}

removeShareCallBack := func() error {
return removeShares(mc)
// Add all vsock
err = vsock.AddHVSockRegistryEntries(append([]vsock.HVSockRegistryEntry{
mc.HyperVHypervisor.ReadyVsock,
mc.HyperVHypervisor.NetworkVSock,
}, sharesVsock...))
if err != nil {
return err
}
callbackFuncs.Add(removeShareCallBack)

removeRegistrySockets := func() error {
removeNetworkAndReadySocketsFromRegistry(mc)
sockets := getVsockShares(mc)
removeSocketsFromRegistry(mc, sockets)
return nil
}
callbackFuncs.Add(removeRegistrySockets)
Expand Down Expand Up @@ -144,7 +149,7 @@ func (h HyperVStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() err

rmFunc := func() error {
// Tear down vsocks
removeNetworkAndReadySocketsFromRegistry(mc)
removeSocketsFromRegistry(mc, []vsock.HVSockRegistryEntry{})

// Remove ignition registry entries - not a fatal error
// for vm removal
Expand Down Expand Up @@ -358,7 +363,7 @@ func (h HyperVStubber) PrepareIgnition(mc *vmconfigs.MachineConfig, ignBuilder *
// simply be derived. So we create the HyperVConfig here.
mc.HyperVHypervisor = new(vmconfigs.HyperVConfig)
var ignOpts ignition.ReadyUnitOpts
readySock, err := vsock.NewHVSockRegistryEntry(mc.Name, vsock.Events)
readySock, err := vsock.CreateHVSockRegistryEntry(mc.Name, vsock.Events)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -461,17 +466,16 @@ func resizeDisk(newSize strongunits.GiB, imagePath *define.VMFile) error {
return nil
}

// removeNetworkAndReadySocketsFromRegistry removes the Network and Ready sockets
// removeSocketsFromRegistry removes the Network, Ready and others (passed by the caller) sockets
// from the Windows Registry
func removeNetworkAndReadySocketsFromRegistry(mc *vmconfigs.MachineConfig) {
// Remove the HVSOCK for networking
if err := mc.HyperVHypervisor.NetworkVSock.Remove(); err != nil {
logrus.Errorf("unable to remove registry entry for %s: %q", mc.HyperVHypervisor.NetworkVSock.KeyName, err)
}

// Remove the HVSOCK for events
if err := mc.HyperVHypervisor.ReadyVsock.Remove(); err != nil {
logrus.Errorf("unable to remove registry entry for %s: %q", mc.HyperVHypervisor.ReadyVsock.KeyName, err)
func removeSocketsFromRegistry(mc *vmconfigs.MachineConfig, others []vsock.HVSockRegistryEntry) {
// remove all sockets from registry
err := vsock.RemoveHVSockRegistryEntries(append([]vsock.HVSockRegistryEntry{
mc.HyperVHypervisor.ReadyVsock,
mc.HyperVHypervisor.NetworkVSock,
}, others...))
if err != nil {
logrus.Errorf("unable to remove registry entries: %q", err)
}
}

Expand Down
23 changes: 10 additions & 13 deletions pkg/machine/hyperv/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
"github.com/sirupsen/logrus"
)

func removeShares(mc *vmconfigs.MachineConfig) error {
var removalErr error
func getVsockShares(mc *vmconfigs.MachineConfig) []vsock.HVSockRegistryEntry {
entries := []vsock.HVSockRegistryEntry{}

for _, mount := range mc.Mounts {
if mount.VSockNumber == nil {
Expand All @@ -29,15 +29,10 @@ func removeShares(mc *vmconfigs.MachineConfig) error {
continue
}

if err := vsockReg.Remove(); err != nil {
if removalErr != nil {
logrus.Errorf("Error removing vsock: %v", removalErr)
}
removalErr = fmt.Errorf("removing vsock %d for mountpoint %s: %w", *mount.VSockNumber, mount.Target, err)
}
entries = append(entries, *vsockReg)
}

return removalErr
return entries
}

func startShares(mc *vmconfigs.MachineConfig) error {
Expand Down Expand Up @@ -70,14 +65,16 @@ func startShares(mc *vmconfigs.MachineConfig) error {
return nil
}

func createShares(mc *vmconfigs.MachineConfig) (err error) {
func createShares(mc *vmconfigs.MachineConfig) ([]vsock.HVSockRegistryEntry, error) {
vsockEntries := []vsock.HVSockRegistryEntry{}
for _, mount := range mc.Mounts {
testVsock, err := vsock.NewHVSockRegistryEntry(mc.Name, vsock.Fileserver)
testVsock, err := vsock.CreateHVSockRegistryEntry(mc.Name, vsock.Fileserver)
if err != nil {
return err
return nil, err
}
vsockEntries = append(vsockEntries, *testVsock)
mount.VSockNumber = &testVsock.Port
logrus.Debugf("Going to share directory %s via 9p on vsock %d", mount.Source, testVsock.Port)
}
return nil
return vsockEntries, nil
}
102 changes: 97 additions & 5 deletions pkg/machine/hyperv/vsock/vsock.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import (
"fmt"
"io"
"net"
"os"
"os/exec"
"strings"

"github.com/Microsoft/go-winio"
"github.com/containers/podman/v5/pkg/machine/sockets"
"github.com/containers/podman/v5/pkg/machine/windows"
"github.com/containers/podman/v5/utils"
"github.com/sirupsen/logrus"
"golang.org/x/sys/windows/registry"
Expand Down Expand Up @@ -83,7 +86,7 @@ type HVSockRegistryEntry struct {
}

// Add creates a new Windows registry entry with string values from the
// HVSockRegistryEntry.
// HVSockRegistryEntry. Must have elevated rights.
func (hv *HVSockRegistryEntry) Add() error {
if err := hv.validate(); err != nil {
return err
Expand Down Expand Up @@ -186,9 +189,9 @@ func findOpenHVSockPort() (uint64, error) {
return 0, errors.New("unable to find a free port for hvsock use")
}

// NewHVSockRegistryEntry is a constructor to make a new registry entry in Windows. After making the new
// object, you must call the add() method to *actually* add it to the Windows registry.
func NewHVSockRegistryEntry(machineName string, purpose HVSockPurpose) (*HVSockRegistryEntry, error) {
// CreateHVSockRegistryEntry is a constructor to make an instance of a registry entry in Windows. After making the new
// object, you must call the add() method or AddHVSockRegistryEntries(...) to *actually* add it to the Windows registry.
func CreateHVSockRegistryEntry(machineName string, purpose HVSockPurpose) (*HVSockRegistryEntry, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this and associated funcs go into libhvee?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you, i can move it if you want. I checked libhvee and it looks like it's dedicated to hyperv but this funcs allow to add/rm entries to/from the Windows registry which are not tight to hyper-v. We use them for hyper-v here but they could be useful in other cases.

// a so-called wildcard entry ... everything from FACB -> 6D3 is MS special sauce
// for a " linux vm". this first segment is hexi for the hvsock port number
// 00000400-FACB-11E6-BD58-64006A7986D3
Expand All @@ -202,10 +205,99 @@ func NewHVSockRegistryEntry(machineName string, purpose HVSockPurpose) (*HVSockR
Port: port,
MachineName: machineName,
}

return &r, nil
}

// NewHVSockRegistryEntry is a constructor to make a new registry entry in Windows. After making the new
// object, it calls the add() method to *actually* add it to the Windows registry.
func NewHVSockRegistryEntry(machineName string, purpose HVSockPurpose) (*HVSockRegistryEntry, error) {
r, err := CreateHVSockRegistryEntry(machineName, purpose)
if err != nil {
return nil, err
}

if err := r.Add(); err != nil {
return nil, err
}
return &r, nil

return r, nil
}

// AddHVSockRegistryEntries allows to *actually* add multiple registry entries to the Windows registry
// As adding an entry to the HKLM path in the Registry requires elevated privileges, this func can be used for bulk insertion so to
// ask the user for elevated rights only once
func AddHVSockRegistryEntries(entries []HVSockRegistryEntry) error {
// create a script which will be executed with elevated rights
script := ""
for _, entry := range entries {
if err := entry.validate(); err != nil {
return err
}
exists, err := entry.exists()
if err != nil {
return err
}
if exists {
return fmt.Errorf("%q: %s", ErrVSockRegistryEntryExists, entry.KeyName)
}
parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, VsockRegistryPath, registry.QUERY_VALUE)
defer func() {
if err := parentKey.Close(); err != nil {
logrus.Error(err)
}
}()
if err != nil {
return err
}

// for each entry it adds a purpose and machineName property
registryPath := fmt.Sprintf("HKLM:\\%s", VsockRegistryPath)
keyPath := fmt.Sprintf("%s\\%s", registryPath, entry.KeyName)

createRegistryKeyCmd := fmt.Sprintf("New-Item -Path '%s' -Name '%s'", registryPath, entry.KeyName)
addPurposePropertyCmd := fmt.Sprintf("New-ItemProperty -Path '%s' -Name '%s' -Value '%s' -PropertyType String", keyPath, HvsockPurpose, entry.Purpose.string())
addMachinePropertyCmd := fmt.Sprintf("New-ItemProperty -Path '%s' -Name '%s' -Value '%s' -PropertyType String", keyPath, HvsockMachineName, entry.MachineName)

script += fmt.Sprintf("%s; %s; %s;", createRegistryKeyCmd, addPurposePropertyCmd, addMachinePropertyCmd)
}

// launch the script in elevated mode
return launchElevated(script)
}

// RemoveHVSockRegistryEntries allows to *actually* remove multiple registry entries from the Windows registry
// As removing an entry from the HKLM path in the Registry requires elevated privileges, this func can be used for bulk deletion so to
// ask the user for elevated rights only once
func RemoveHVSockRegistryEntries(entries []HVSockRegistryEntry) error {
// create a script which will be executed with elevated rights
script := ""
for _, entry := range entries {
// for each entry it calculate the path and the script to remove it
registryPath := fmt.Sprintf("HKLM:\\%s", VsockRegistryPath)
keyPath := fmt.Sprintf("%s\\%s", registryPath, entry.KeyName)

removeRegistryKeyCmd := fmt.Sprintf("Remove-Item -Path '%s' -Force -Recurse", keyPath)

script += fmt.Sprintf("%s;", removeRegistryKeyCmd)
}

// launch the script in elevated mode
return launchElevated(script)
}

func launchElevated(args string) error {
psPath, err := exec.LookPath("powershell.exe")
if err != nil {
return err
}

d, err := os.Getwd()
if err != nil {
return err
}

return windows.LaunchElevatedWait(psPath, d, args)
}

func portToKeyName(port uint64) string {
Expand Down
4 changes: 2 additions & 2 deletions pkg/machine/provider/platform_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ func Get() (vmconfigs.VMProvider, error) {
case define.WSLVirt:
return new(wsl.WSLStubber), nil
case define.HyperVVirt:
if !wsl.HasAdminRights() {
return nil, fmt.Errorf("hyperv machines require admin authority")
if !hyperv.HasHyperVAdminRights() {
return nil, fmt.Errorf("hyperv machines require hyperv admin authority")
}
return new(hyperv.HyperVStubber), nil
default:
Expand Down
Loading
Loading