Skip to content

Commit c43582b

Browse files
committed
propose running init/rm command on hyperv machine in elevated mode
This commit adds automatic UAC elevation prompts for HyperV machine init/rm actions when administrator privileges are required. Previously, users had to manually run Podman as administrator when creating the first machine or removing the last machine, which requires Windows Registry modifications. Signed-off-by: lstocchi <lstocchi@redhat.com>
1 parent 34931c8 commit c43582b

8 files changed

Lines changed: 98 additions & 11 deletions

File tree

cmd/podman/machine/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ func initMachine(cmd *cobra.Command, args []string) error {
287287
// Examples:
288288
// - a user has chosen to perform their own reboot
289289
// - reexec for limited admin operations, returning to parent
290-
if errors.Is(err, define.ErrInitRelaunchAttempt) {
290+
if errors.Is(err, define.ErrRelaunchAttempt) {
291291
return nil
292292
}
293293
return err

cmd/podman/machine/rm.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
package machine
44

55
import (
6+
"errors"
7+
68
"github.com/containers/podman/v6/cmd/podman/registry"
79
"github.com/containers/podman/v6/libpod/events"
810
"github.com/containers/podman/v6/pkg/machine"
11+
"github.com/containers/podman/v6/pkg/machine/define"
912
"github.com/containers/podman/v6/pkg/machine/shim"
1013
"github.com/spf13/cobra"
1114
)
@@ -38,6 +41,13 @@ func init() {
3841

3942
imageFlagName := "save-image"
4043
flags.BoolVar(&destroyOptions.SaveImage, imageFlagName, false, "Do not delete the image file")
44+
45+
flags.BoolVar(
46+
&destroyOptions.ReExec,
47+
"reexec", false,
48+
"process was rexeced",
49+
)
50+
_ = flags.MarkHidden("reexec")
4151
}
4252

4353
func rm(_ *cobra.Command, args []string) error {
@@ -53,6 +63,13 @@ func rm(_ *cobra.Command, args []string) error {
5363
}
5464

5565
if err := shim.Remove(mc, vmProvider, destroyOptions); err != nil {
66+
// The removal is partially complete and podman should
67+
// exit gracefully with no error and no success message.
68+
// Examples:
69+
// - reexec for limited admin operations, returning to parent
70+
if errors.Is(err, define.ErrRelaunchAttempt) {
71+
return nil
72+
}
5673
return err
5774
}
5875
newMachineEvent(events.Remove, events.Event{Name: vmName})

pkg/machine/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ type StartOptions struct {
7474
type StopOptions struct{}
7575

7676
type RemoveOptions struct {
77+
ReExec bool
7778
Force bool
7879
SaveImage bool
7980
SaveIgnition bool

pkg/machine/define/errors.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66
)
77

88
var (
9-
ErrWrongState = errors.New("VM in wrong state to perform action")
10-
ErrNotImplemented = errors.New("functionality not implemented")
11-
ErrInitRelaunchAttempt = errors.New("stopping execution: 'init' relaunched with --reexec flag to reinitialize the VM")
12-
ErrRebootInitiated = errors.New("system reboot initiated")
9+
ErrWrongState = errors.New("VM in wrong state to perform action")
10+
ErrNotImplemented = errors.New("functionality not implemented")
11+
ErrRelaunchAttempt = errors.New("stopping execution: command relaunched with --reexec flag for elevated privileges")
12+
ErrRebootInitiated = errors.New("system reboot initiated")
1313
)
1414

1515
type ErrVMAlreadyExists struct {

pkg/machine/hyperv/stubber.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ func (h HyperVStubber) CreateVM(_ define.CreateVMOpts, mc *vmconfigs.MachineConf
6262
// This is to prevent a non-admin user from creating the first machine
6363
// which would require adding vsock entries into the Windows Registry.
6464
if err := h.canCreate(); err != nil {
65+
// If it returns ErrHypervRegistryInitRequiresElevation and we're not already re-executing,
66+
// offer to elevate automatically if user is in admin group
67+
if err == ErrHypervRegistryInitRequiresElevation && !windows.IsReExecuting() && windows.IsInAdministratorsGroup() {
68+
message := "To initialize the first Hyper-V machine, Podman requires admin rights to set up the Windows Registry.\n\n" +
69+
"A UAC prompt will appear asking for your approval.\n\n" +
70+
"Would you like to continue?"
71+
return launchElevate(message)
72+
}
6573
return err
6674
}
6775

@@ -178,6 +186,14 @@ func (h HyperVStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() err
178186
// This is to prevent a non-admin user from deleting the last machine
179187
// which would require removal of vsock entries from the Windows Registry.
180188
if err := h.canRemove(mc); err != nil {
189+
// If we get ErrHypervRegistryRemoveRequiresElevation and we're not already re-executing,
190+
// and the user has admin rights (is in admin group), offer to elevate automatically
191+
if err == ErrHypervRegistryRemoveRequiresElevation && !windows.IsReExecuting() && windows.IsInAdministratorsGroup() {
192+
message := "Removing this Hyper-V machine requires admin rights to clean up the Windows Registry.\n\n" +
193+
"A UAC prompt will appear asking for your approval.\n\n" +
194+
"Would you like to continue?"
195+
return nil, nil, launchElevate(message)
196+
}
181197
return nil, nil, err
182198
}
183199

@@ -236,6 +252,21 @@ func (h HyperVStubber) canCreate() error {
236252
return ErrHypervUserNotInAdminGroup
237253
}
238254

255+
// launchElevate attempts to automatically re-run the command as administrator
256+
// This is similar to how WSL handles elevation.
257+
func launchElevate(message string) error {
258+
if windows.MessageBox(message, "Podman Machine", false) != 1 {
259+
return errors.New("elevation process cancelled by user. Please rerun the command as administrator.")
260+
}
261+
262+
err := windows.RelaunchElevatedWait()
263+
if err != nil {
264+
fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
265+
return fmt.Errorf("Elevated process failed with error: %w", err)
266+
}
267+
return define.ErrRelaunchAttempt
268+
}
269+
239270
func isLegacyMachine(mc *vmconfigs.MachineConfig) bool {
240271
return mc.HyperVHypervisor != nil && mc.HyperVHypervisor.ReadyVsock.MachineName != ""
241272
}
@@ -553,6 +584,12 @@ func (h HyperVStubber) PrepareIgnition(mc *vmconfigs.MachineConfig, _ *ignition.
553584
readySock, err := vsock.LoadHVSockRegistryEntryByPurpose(vsock.Events)
554585
if err != nil {
555586
if !windows.HasAdminRights() {
587+
if !windows.IsReExecuting() && windows.IsInAdministratorsGroup() {
588+
message := "Initializing the first Hyper-V machine requires admin rights to set up the Windows Registry.\n\n" +
589+
"A UAC prompt will appear asking for your approval.\n\n" +
590+
"Would you like to continue?"
591+
return nil, launchElevate(message)
592+
}
556593
return nil, ErrHypervRegistryInitRequiresElevation
557594
}
558595
readySock, err = vsock.NewHVSockRegistryEntry(vsock.Events)

pkg/machine/shim/host.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,8 +732,10 @@ func Remove(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, opts machine.R
732732
if err != nil {
733733
return err
734734
}
735-
mc.Lock()
736-
defer mc.Unlock()
735+
if !opts.ReExec {
736+
mc.Lock()
737+
defer mc.Unlock()
738+
}
737739
if err := mc.Refresh(); err != nil {
738740
return fmt.Errorf("reload config: %w", err)
739741
}

pkg/machine/windows/util_windows.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"errors"
55
"fmt"
66
"os"
7+
"os/user"
8+
"slices"
79
"strings"
810
"syscall"
911
"unsafe"
@@ -12,6 +14,24 @@ import (
1214
"golang.org/x/sys/windows"
1315
)
1416

17+
// IsInAdministratorsGroup checks if the current user is a member of the Administrators group,
18+
// regardless of whether the current process is elevated. This can be used to determine
19+
// if the user can elevate privileges via UAC.
20+
func IsInAdministratorsGroup() bool {
21+
u, err := user.Current()
22+
if nil != err {
23+
return false
24+
}
25+
ids, err := u.GroupIds()
26+
if nil != err {
27+
return false
28+
}
29+
// S-1-5-32-544 is the SID for the Administrators group
30+
// see: https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers
31+
return slices.Contains(ids, "S-1-5-32-544")
32+
}
33+
34+
// HasAdminRights checks if the current process has administrator privileges.
1535
func HasAdminRights() bool {
1636
var sid *windows.SID
1737

@@ -181,3 +201,13 @@ func wrapMaybef(err error, format string, args ...any) error {
181201

182202
return fmt.Errorf(format, args...)
183203
}
204+
205+
// IsReExecuting checks if the current process was re-executed with --reexec flag
206+
func IsReExecuting() bool {
207+
for _, arg := range os.Args {
208+
if arg == "--reexec" {
209+
return true
210+
}
211+
}
212+
return false
213+
}

pkg/machine/wsl/machine.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ func attemptFeatureInstall(reExec, admin bool) error {
325325
"If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command."
326326

327327
if !reExec && winutil.MessageBox(message, "Podman Machine", false) != 1 {
328-
return fmt.Errorf("the WSL installation aborted: %w", define.ErrInitRelaunchAttempt)
328+
return fmt.Errorf("the WSL installation aborted: %w", define.ErrRelaunchAttempt)
329329
}
330330

331331
if !reExec && !admin {
@@ -343,16 +343,16 @@ func launchElevate(operation string) error {
343343
if eerr, ok := err.(*winutil.ExitCodeError); ok {
344344
if eerr.Code == ErrorSuccessRebootRequired {
345345
fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
346-
return define.ErrInitRelaunchAttempt
346+
return define.ErrRelaunchAttempt
347347
}
348348
}
349349

350350
fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
351351
dumpOutputFile()
352352
fmt.Fprintf(os.Stderr, wslInstallError, operation)
353-
return fmt.Errorf("%w: %w", err, define.ErrInitRelaunchAttempt)
353+
return fmt.Errorf("%w: %w", err, define.ErrRelaunchAttempt)
354354
}
355-
return define.ErrInitRelaunchAttempt
355+
return define.ErrRelaunchAttempt
356356
}
357357

358358
func installWsl() error {

0 commit comments

Comments
 (0)