Skip to content

Commit bc58a14

Browse files
committed
Execute add/remove registry entries in elevated mode with hyperv
Adding/removing a key in the Windows registry requires elevated rights. For this reason, using podman with hyperv had the contraint 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. Signed-off-by: lstocchi <[email protected]>
1 parent 7b35f4f commit bc58a14

File tree

7 files changed

+253
-135
lines changed

7 files changed

+253
-135
lines changed

pkg/machine/hyperv/stubber.go

+23-19
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,31 @@ func (h HyperVStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineC
5555
Memory: uint64(mc.Resources.Memory),
5656
}
5757

58-
networkHVSock, err := vsock.NewHVSockRegistryEntry(mc.Name, vsock.Network)
58+
networkHVSock, err := vsock.CreateHVSockRegistryEntry(mc.Name, vsock.Network)
5959
if err != nil {
6060
return err
6161
}
6262

6363
mc.HyperVHypervisor.NetworkVSock = *networkHVSock
6464

65-
// Add vsock port numbers to mounts
66-
err = createShares(mc)
65+
// Create vsock port numbers to mounts
66+
sharesVsock, err := createShares(mc)
6767
if err != nil {
6868
return err
6969
}
7070

71-
removeShareCallBack := func() error {
72-
return removeShares(mc)
71+
// Add all vsock
72+
err = vsock.AddHVSockRegistryEntries(append([]vsock.HVSockRegistryEntry{
73+
mc.HyperVHypervisor.ReadyVsock,
74+
mc.HyperVHypervisor.NetworkVSock,
75+
}, sharesVsock...))
76+
if err != nil {
77+
return err
7378
}
74-
callbackFuncs.Add(removeShareCallBack)
7579

7680
removeRegistrySockets := func() error {
77-
removeNetworkAndReadySocketsFromRegistry(mc)
81+
sockets := getVsockShares(mc)
82+
removeSocketsFromRegistry(mc, sockets)
7883
return nil
7984
}
8085
callbackFuncs.Add(removeRegistrySockets)
@@ -144,7 +149,7 @@ func (h HyperVStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() err
144149

145150
rmFunc := func() error {
146151
// Tear down vsocks
147-
removeNetworkAndReadySocketsFromRegistry(mc)
152+
removeSocketsFromRegistry(mc, []vsock.HVSockRegistryEntry{})
148153

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

464-
// removeNetworkAndReadySocketsFromRegistry removes the Network and Ready sockets
469+
// removeSocketsFromRegistry removes the Network, Ready and others (passed by the caller) sockets
465470
// from the Windows Registry
466-
func removeNetworkAndReadySocketsFromRegistry(mc *vmconfigs.MachineConfig) {
467-
// Remove the HVSOCK for networking
468-
if err := mc.HyperVHypervisor.NetworkVSock.Remove(); err != nil {
469-
logrus.Errorf("unable to remove registry entry for %s: %q", mc.HyperVHypervisor.NetworkVSock.KeyName, err)
470-
}
471-
472-
// Remove the HVSOCK for events
473-
if err := mc.HyperVHypervisor.ReadyVsock.Remove(); err != nil {
474-
logrus.Errorf("unable to remove registry entry for %s: %q", mc.HyperVHypervisor.ReadyVsock.KeyName, err)
471+
func removeSocketsFromRegistry(mc *vmconfigs.MachineConfig, others []vsock.HVSockRegistryEntry) {
472+
// remove all sockets from registry
473+
err := vsock.RemoveHVSockRegistryEntries(append([]vsock.HVSockRegistryEntry{
474+
mc.HyperVHypervisor.ReadyVsock,
475+
mc.HyperVHypervisor.NetworkVSock,
476+
}, others...))
477+
if err != nil {
478+
logrus.Errorf("unable to remove registry entries: %q", err)
475479
}
476480
}
477481

pkg/machine/hyperv/volumes.go

+27-4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ func removeShares(mc *vmconfigs.MachineConfig) error {
4040
return removalErr
4141
}
4242

43+
func getVsockShares(mc *vmconfigs.MachineConfig) []vsock.HVSockRegistryEntry {
44+
entries := []vsock.HVSockRegistryEntry{}
45+
46+
for _, mount := range mc.Mounts {
47+
if mount.VSockNumber == nil {
48+
// nothing to do if the vsock number was never defined
49+
continue
50+
}
51+
52+
vsockReg, err := vsock.LoadHVSockRegistryEntry(*mount.VSockNumber)
53+
if err != nil {
54+
logrus.Debugf("Vsock %d for mountpoint %s does not have a valid registry entry, skipping removal", *mount.VSockNumber, mount.Target)
55+
continue
56+
}
57+
58+
entries = append(entries, *vsockReg)
59+
}
60+
61+
return entries
62+
}
63+
4364
func startShares(mc *vmconfigs.MachineConfig) error {
4465
for _, mount := range mc.Mounts {
4566
var args []string
@@ -70,14 +91,16 @@ func startShares(mc *vmconfigs.MachineConfig) error {
7091
return nil
7192
}
7293

73-
func createShares(mc *vmconfigs.MachineConfig) (err error) {
94+
func createShares(mc *vmconfigs.MachineConfig) ([]vsock.HVSockRegistryEntry, error) {
95+
vsockEntries := []vsock.HVSockRegistryEntry{}
7496
for _, mount := range mc.Mounts {
75-
testVsock, err := vsock.NewHVSockRegistryEntry(mc.Name, vsock.Fileserver)
97+
testVsock, err := vsock.CreateHVSockRegistryEntry(mc.Name, vsock.Fileserver)
7698
if err != nil {
77-
return err
99+
return nil, err
78100
}
101+
vsockEntries = append(vsockEntries, *testVsock)
79102
mount.VSockNumber = &testVsock.Port
80103
logrus.Debugf("Going to share directory %s via 9p on vsock %d", mount.Source, testVsock.Port)
81104
}
82-
return nil
105+
return vsockEntries, nil
83106
}

pkg/machine/hyperv/vsock/vsock.go

+94-5
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import (
77
"fmt"
88
"io"
99
"net"
10+
"os"
11+
"os/exec"
1012
"strings"
1113

1214
"github.com/Microsoft/go-winio"
1315
"github.com/containers/podman/v5/pkg/machine/sockets"
16+
"github.com/containers/podman/v5/pkg/machine/wsl/wutil"
1417
"github.com/containers/podman/v5/utils"
1518
"github.com/sirupsen/logrus"
1619
"golang.org/x/sys/windows/registry"
@@ -83,7 +86,7 @@ type HVSockRegistryEntry struct {
8386
}
8487

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

189-
// NewHVSockRegistryEntry is a constructor to make a new registry entry in Windows. After making the new
190-
// object, you must call the add() method to *actually* add it to the Windows registry.
191-
func NewHVSockRegistryEntry(machineName string, purpose HVSockPurpose) (*HVSockRegistryEntry, error) {
192+
// CreateHVSockRegistryEntry is a constructor to make an instance of a registry entry in Windows. After making the new
193+
// object, you must call the add() method or AddHVSockRegistryEntries(...) to *actually* add it to the Windows registry.
194+
func CreateHVSockRegistryEntry(machineName string, purpose HVSockPurpose) (*HVSockRegistryEntry, error) {
192195
// a so-called wildcard entry ... everything from FACB -> 6D3 is MS special sauce
193196
// for a " linux vm". this first segment is hexi for the hvsock port number
194197
// 00000400-FACB-11E6-BD58-64006A7986D3
@@ -202,10 +205,96 @@ func NewHVSockRegistryEntry(machineName string, purpose HVSockPurpose) (*HVSockR
202205
Port: port,
203206
MachineName: machineName,
204207
}
208+
209+
return &r, nil
210+
}
211+
212+
// NewHVSockRegistryEntry is a constructor to make a new registry entry in Windows. After making the new
213+
// object, it calls the add() method to *actually* add it to the Windows registry.
214+
func NewHVSockRegistryEntry(machineName string, purpose HVSockPurpose) (*HVSockRegistryEntry, error) {
215+
r, err := CreateHVSockRegistryEntry(machineName, purpose)
216+
if err != nil {
217+
return nil, err
218+
}
219+
205220
if err := r.Add(); err != nil {
206221
return nil, err
207222
}
208-
return &r, nil
223+
224+
return r, nil
225+
}
226+
227+
// AddHVSockRegistryEntries allows to *actually* add multiple registry entries to the Windows registry
228+
// As adding an entry to the HKLM path in the Registry requires elevated privileges, this func can be used for bulk insertion so to
229+
// ask the user for elevated rights only once
230+
func AddHVSockRegistryEntries(entries []HVSockRegistryEntry) error {
231+
// create a script which will be executed with elevated rights
232+
script := ""
233+
for _, entry := range entries {
234+
if err := entry.validate(); err != nil {
235+
return err
236+
}
237+
exists, err := entry.exists()
238+
if err != nil {
239+
return err
240+
}
241+
if exists {
242+
return fmt.Errorf("%q: %s", ErrVSockRegistryEntryExists, entry.KeyName)
243+
}
244+
parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, VsockRegistryPath, registry.QUERY_VALUE)
245+
defer func() {
246+
if err := parentKey.Close(); err != nil {
247+
logrus.Error(err)
248+
}
249+
}()
250+
if err != nil {
251+
return err
252+
}
253+
254+
// for each entry it adds a purpose and machineName property
255+
registryPath := fmt.Sprintf("HKLM:\\%s", VsockRegistryPath)
256+
keyPath := fmt.Sprintf("%s\\%s", registryPath, entry.KeyName)
257+
258+
createRegistryKeyCmd := fmt.Sprintf("New-Item -Path '%s' -Name '%s'", registryPath, entry.KeyName)
259+
addPurposePropertyCmd := fmt.Sprintf("New-ItemProperty -Path '%s' -Name '%s' -Value '%s' -PropertyType String", keyPath, HvsockPurpose, entry.Purpose.string())
260+
addMachinePropertyCmd := fmt.Sprintf("New-ItemProperty -Path '%s' -Name '%s' -Value '%s' -PropertyType String", keyPath, HvsockMachineName, entry.MachineName)
261+
262+
script += fmt.Sprintf("%s; %s; %s;", createRegistryKeyCmd, addPurposePropertyCmd, addMachinePropertyCmd)
263+
}
264+
265+
// launch the script in elevated mode
266+
return launchElevated(script)
267+
}
268+
269+
// RemoveHVSockRegistryEntries allows to *actually* remove multiple registry entries from the Windows registry
270+
// As removing an entry from the HKLM path in the Registry requires elevated privileges, this func can be used for bulk deletion so to
271+
// ask the user for elevated rights only once
272+
func RemoveHVSockRegistryEntries(entries []HVSockRegistryEntry) error {
273+
// create a script which will be executed with elevated rights
274+
script := ""
275+
for _, entry := range entries {
276+
// for each entry it calculate the path and the script to remove it
277+
registryPath := fmt.Sprintf("HKLM:\\%s", VsockRegistryPath)
278+
keyPath := fmt.Sprintf("%s\\%s", registryPath, entry.KeyName)
279+
280+
removeRegistryKeyCmd := fmt.Sprintf("Remove-Item -Path '%s' -Force -Recurse", keyPath)
281+
282+
script += fmt.Sprintf("%s;", removeRegistryKeyCmd)
283+
}
284+
285+
// launch the script in elevated mode
286+
return launchElevated(script)
287+
}
288+
289+
func launchElevated(args string) error {
290+
psPath, err := exec.LookPath("powershell.exe")
291+
if err != nil {
292+
return err
293+
}
294+
295+
d, _ := os.Getwd()
296+
297+
return wutil.LaunchElevatedWait(psPath, d, args)
209298
}
210299

211300
func portToKeyName(port uint64) string {

pkg/machine/provider/platform_windows.go

-3
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ func Get() (vmconfigs.VMProvider, error) {
3434
case define.WSLVirt:
3535
return new(wsl.WSLStubber), nil
3636
case define.HyperVVirt:
37-
if !wsl.HasAdminRights() {
38-
return nil, fmt.Errorf("hyperv machines require admin authority")
39-
}
4037
return new(hyperv.HyperVStubber), nil
4138
default:
4239
return nil, fmt.Errorf("unsupported virtualization provider: `%s`", resolvedVMType.String())

pkg/machine/wsl/machine.go

+2-10
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,6 @@ var (
3535
vmtype = define.WSLVirt
3636
)
3737

38-
type ExitCodeError struct {
39-
code uint
40-
}
41-
42-
func (e *ExitCodeError) Error() string {
43-
return fmt.Sprintf("Process failed with exit code: %d", e.code)
44-
}
45-
4638
//nolint:unused
4739
func getConfigPath(name string) (string, error) {
4840
return getConfigPathExt(name, "json")
@@ -374,8 +366,8 @@ func launchElevate(operation string) error {
374366
}
375367
err := relaunchElevatedWait()
376368
if err != nil {
377-
if eerr, ok := err.(*ExitCodeError); ok {
378-
if eerr.code == ErrorSuccessRebootRequired {
369+
if eerr, ok := err.(*wutil.ExitCodeError); ok {
370+
if eerr.Code == ErrorSuccessRebootRequired {
379371
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
380372
return nil
381373
}

0 commit comments

Comments
 (0)