Skip to content

Commit

Permalink
Execute add/remove registry entries in elevated mode with hyperv
Browse files Browse the repository at this point in the history
Adding/removing a key in the Windows registry requires elevated rights. For this reason, using podman with hyperv had the constraint to run it as admin otherwise it was not possible to init/rm a hyperv machine.

This patch adds a couple of functions to add/remove registry entries in bulk in privileged mode. When creating/deleting a machine the user is asked only once to elevate the rights to perform the action. So now podman can be started without being admin.

The only constraint is that you must be a member of the Hyper-V
administratos group.

This will also be leveraged by podman desktop so users could switch from wsl to hyperv without restarting desktop in elevated mode.

Signed-off-by: lstocchi <[email protected]>
  • Loading branch information
lstocchi committed Dec 16, 2024
1 parent 7b35f4f commit ed47c52
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 145 deletions.
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) {
// 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

0 comments on commit ed47c52

Please sign in to comment.