Introduce podman machine init --root=t|f and podman machine set --root=t|f

Switch default to rootless for mac and windows

Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
Jason T. Greene
2022-01-29 03:10:28 -06:00
parent 8f5ba05ec4
commit c74f8f04fd
10 changed files with 318 additions and 40 deletions

View File

@ -26,7 +26,7 @@ var (
var ( var (
initOpts = machine.InitOptions{} initOpts = machine.InitOptions{}
defaultMachineName = "podman-machine-default" defaultMachineName = machine.DefaultMachineName
now bool now bool
) )
@ -99,6 +99,9 @@ func init() {
IgnitionPathFlagName := "ignition-path" IgnitionPathFlagName := "ignition-path"
flags.StringVar(&initOpts.IgnitionPath, IgnitionPathFlagName, "", "Path to ignition file") flags.StringVar(&initOpts.IgnitionPath, IgnitionPathFlagName, "", "Path to ignition file")
_ = initCmd.RegisterFlagCompletionFunc(IgnitionPathFlagName, completion.AutocompleteDefault) _ = initCmd.RegisterFlagCompletionFunc(IgnitionPathFlagName, completion.AutocompleteDefault)
rootfulFlagName := "rootful"
flags.BoolVar(&initOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container exectution")
} }
// TODO should we allow for a users to append to the qemu cmdline? // TODO should we allow for a users to append to the qemu cmdline?

56
cmd/podman/machine/set.go Normal file
View File

@ -0,0 +1,56 @@
// +build amd64 arm64
package machine
import (
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/machine"
"github.com/spf13/cobra"
)
var (
setCmd = &cobra.Command{
Use: "set [options] [NAME]",
Short: "Sets a virtual machine setting",
Long: "Sets an updatable virtual machine setting",
RunE: setMachine,
Args: cobra.MaximumNArgs(1),
Example: `podman machine set --root=false`,
ValidArgsFunction: completion.AutocompleteNone,
}
)
var (
setOpts = machine.SetOptions{}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: setCmd,
Parent: machineCmd,
})
flags := setCmd.Flags()
rootfulFlagName := "rootful"
flags.BoolVar(&setOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container execution")
}
func setMachine(cmd *cobra.Command, args []string) error {
var (
vm machine.VM
err error
)
vmName := defaultMachineName
if len(args) > 0 && len(args[0]) > 0 {
vmName = args[0]
}
provider := getSystemDefaultProvider()
vm, err = provider.LoadVMByName(vmName)
if err != nil {
return err
}
return vm.Set(vmName, setOpts)
}

View File

@ -55,6 +55,14 @@ Memory (in MB).
Start the virtual machine immediately after it has been initialized. Start the virtual machine immediately after it has been initialized.
#### **--rootful**=*true|false*
Whether this machine should prefer rootful (`true`) or rootless (`false`)
container execution. This option will also determine the remote connection default
if there is no existing remote connection configurations.
API forwarding, if available, will follow this setting.
#### **--timezone** #### **--timezone**
Set the timezone for the machine and containers. Valid values are `local` or Set the timezone for the machine and containers. Valid values are `local` or
@ -84,6 +92,7 @@ Print usage statement.
``` ```
$ podman machine init $ podman machine init
$ podman machine init myvm $ podman machine init myvm
$ podman machine init --rootful
$ podman machine init --disk-size 50 $ podman machine init --disk-size 50
$ podman machine init --memory=1024 myvm $ podman machine init --memory=1024 myvm
$ podman machine init -v /Users:/mnt/Users $ podman machine init -v /Users:/mnt/Users

View File

@ -0,0 +1,59 @@
% podman-machine-set(1)
## NAME
podman\-machine\-set - Sets a virtual machine setting
## SYNOPSIS
**podman machine set** [*options*] [*name*]
## DESCRIPTION
Sets an updatable virtual machine setting.
Options mirror values passed to `podman machine init`. Only a limited
subset can be changed after machine initialization.
## OPTIONS
#### **--rootful**=*true|false*
Whether this machine should prefer rootful (`true`) or rootless (`false`)
container execution. This option will also update the current podman
remote connection default if it is currently pointing at the specified
machine name (or `podman-machine-default` if no name is specified).
API forwarding, if available, will follow this setting.
#### **--help**
Print usage statement.
## EXAMPLES
To switch the default VM `podman-machine-default` from rootless to rootful:
```
$ podman machine set --rootful
```
or more explicitly:
```
$ podman machine set --rootful=true
```
To switch the default VM `podman-machine-default` from rootful to rootless:
```
$ podman machine set --rootful=false
```
To switch the VM `myvm` from rootless to rootful:
```
$ podman machine set --rootful myvm
```
## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)**
## HISTORY
February 2022, Originally compiled by Jason Greene <jason.greene@redhat.com>

View File

@ -16,6 +16,7 @@ podman\-machine - Manage Podman's virtual machine
| init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine | | init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine |
| list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines | | list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines |
| rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine | | rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine |
| set | [podman-machine-set(1)](podman-machine-set.1.md) | Sets a virtual machine setting |
| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | | ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine |
| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | | start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine |
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | | stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine |

View File

@ -27,6 +27,7 @@ type InitOptions struct {
URI url.URL URI url.URL
Username string Username string
ReExec bool ReExec bool
Rootful bool
} }
type QemuMachineStatus = string type QemuMachineStatus = string
@ -35,7 +36,8 @@ const (
// Running indicates the qemu vm is running // Running indicates the qemu vm is running
Running QemuMachineStatus = "running" Running QemuMachineStatus = "running"
// Stopped indicates the vm has stopped // Stopped indicates the vm has stopped
Stopped QemuMachineStatus = "stopped" Stopped QemuMachineStatus = "stopped"
DefaultMachineName string = "podman-machine-default"
) )
type Provider interface { type Provider interface {
@ -89,6 +91,10 @@ type ListResponse struct {
IdentityPath string IdentityPath string
} }
type SetOptions struct {
Rootful bool
}
type SSHOptions struct { type SSHOptions struct {
Username string Username string
Args []string Args []string
@ -107,6 +113,7 @@ type RemoveOptions struct {
type VM interface { type VM interface {
Init(opts InitOptions) (bool, error) Init(opts InitOptions) (bool, error)
Remove(name string, opts RemoveOptions) (string, func() error, error) Remove(name string, opts RemoveOptions) (string, func() error, error)
Set(name string, opts SetOptions) error
SSH(name string, opts SSHOptions) error SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error Start(name string, opts StartOptions) error
Stop(name string, opts StopOptions) error Stop(name string, opts StopOptions) error

View File

@ -39,6 +39,31 @@ func AddConnection(uri fmt.Stringer, name, identity string, isDefault bool) erro
return cfg.Write() return cfg.Write()
} }
func AnyConnectionDefault(name ...string) (bool, error) {
cfg, err := config.ReadCustomConfig()
if err != nil {
return false, err
}
for _, n := range name {
if n == cfg.Engine.ActiveService {
return true, nil
}
}
return false, nil
}
func ChangeDefault(name string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
cfg.Engine.ActiveService = name
return cfg.Write()
}
func RemoveConnection(name string) error { func RemoveConnection(name string) error {
cfg, err := config.ReadCustomConfig() cfg, err := config.ReadCustomConfig()
if err != nil { if err != nil {

View File

@ -33,6 +33,8 @@ type MachineVM struct {
QMPMonitor Monitor QMPMonitor Monitor
// RemoteUsername of the vm user // RemoteUsername of the vm user
RemoteUsername string RemoteUsername string
// Whether this machine should run in a rootful or rootless manner
Rootful bool
} }
type Mount struct { type Mount struct {

View File

@ -5,14 +5,15 @@ package qemu
import ( import (
"bufio" "bufio"
"encoding/base64"
"context" "context"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/fs" "io/fs"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -166,14 +167,8 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
key string key string
) )
sshDir := filepath.Join(homedir.Get(), ".ssh") sshDir := filepath.Join(homedir.Get(), ".ssh")
// GetConfDir creates the directory so no need to check for
// its existence
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return false, err
}
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
v.IdentityPath = filepath.Join(sshDir, v.Name) v.IdentityPath = filepath.Join(sshDir, v.Name)
v.Rootful = opts.Rootful
switch opts.ImagePath { switch opts.ImagePath {
case "testing", "next", "stable", "": case "testing", "next", "stable", "":
@ -256,29 +251,33 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
// This kind of stinks but no other way around this r/n // This kind of stinks but no other way around this r/n
if len(opts.IgnitionPath) < 1 { if len(opts.IgnitionPath) < 1 {
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername) uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
if err := machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil { uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
return false, err identity := filepath.Join(sshDir, v.Name)
uris := []url.URL{uri, uriRoot}
names := []string{v.Name, v.Name + "-root"}
// The first connection defined when connections is empty will become the default
// regardless of IsDefault, so order according to rootful
if opts.Rootful {
uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
} }
uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root") for i := 0; i < 2; i++ {
if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil { if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil {
return false, err return false, err
}
} }
} else { } else {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman") fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
} }
// Write the JSON file // Write the JSON file
b, err := json.MarshalIndent(v, "", " ") v.writeConfig()
if err != nil {
return false, err
}
if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
return false, err
}
// User has provided ignition file so keygen // User has provided ignition file so keygen
// will be skipped. // will be skipped.
if len(opts.IgnitionPath) < 1 { if len(opts.IgnitionPath) < 1 {
var err error
key, err = machine.CreateSSHKeys(v.IdentityPath) key, err = machine.CreateSSHKeys(v.IdentityPath)
if err != nil { if err != nil {
return false, err return false, err
@ -325,6 +324,30 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
return err == nil, err return err == nil, err
} }
func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
if v.Rootful == opts.Rootful {
return nil
}
changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
if err != nil {
return err
}
if changeCon {
newDefault := v.Name
if opts.Rootful {
newDefault += "-root"
}
if err := machine.ChangeDefault(newDefault); err != nil {
return err
}
}
v.Rootful = opts.Rootful
return v.writeConfig()
}
// Start executes the qemu command line and forks it // Start executes the qemu command line and forks it
func (v *MachineVM) Start(name string, _ machine.StartOptions) error { func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
var ( var (
@ -457,7 +480,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
} }
} }
printAPIForwardInstructions(forwardState, forwardSock) waitAPIAndPrintInfo(forwardState, forwardSock, v.Rootful, v.Name)
return nil return nil
} }
@ -929,9 +952,17 @@ func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwa
return cmd, "", noForwarding return cmd, "", noForwarding
} }
destSock := "/run/user/1000/podman/podman.sock"
forwardUser := "core"
if v.Rootful {
destSock = "/run/podman/podman.sock"
forwardUser = "root"
}
cmd = append(cmd, []string{"-forward-sock", socket}...) cmd = append(cmd, []string{"-forward-sock", socket}...)
cmd = append(cmd, []string{"-forward-dest", "/run/podman/podman.sock"}...) cmd = append(cmd, []string{"-forward-dest", destSock}...)
cmd = append(cmd, []string{"-forward-user", "root"}...) cmd = append(cmd, []string{"-forward-user", forwardUser}...)
cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...) cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...)
link := filepath.Join(filepath.Dir(filepath.Dir(socket)), "podman.sock") link := filepath.Join(filepath.Dir(filepath.Dir(socket)), "podman.sock")
@ -1031,19 +1062,32 @@ func waitAndPingAPI(sock string) {
} }
} }
func printAPIForwardInstructions(forwardState apiForwardingState, forwardSock string) { func waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string, rootFul bool, name string) {
if forwardState != noForwarding { if forwardState != noForwarding {
waitAndPingAPI(forwardSock) waitAndPingAPI(forwardSock)
if !rootFul {
fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
suffix := ""
if name != machine.DefaultMachineName {
suffix = " " + name
}
fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
}
fmt.Printf("API forwarding listening on: %s\n", forwardSock) fmt.Printf("API forwarding listening on: %s\n", forwardSock)
if forwardState == dockerGlobal { if forwardState == dockerGlobal {
fmt.Printf("\nDocker API clients default to this address. You do not need to set DOCKER_HOST.\n\n") fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n")
} else { } else {
stillString := "still " stillString := "still "
switch forwardState { switch forwardState {
case notInstalled: case notInstalled:
fmt.Printf("\nThe system helper service is not installed; the default Docker API socket address can't be used by podman.\n") fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n")
fmt.Printf("address can't be used by podman. ")
if helper := findClaimHelper(); len(helper) > 0 { if helper := findClaimHelper(); len(helper) > 0 {
fmt.Printf("If you would like to install it run the following command:\n") fmt.Printf("If you would like to install it run the\nfollowing command:\n")
fmt.Printf("\n\tsudo %s install\n\n", helper) fmt.Printf("\n\tsudo %s install\n\n", helper)
} }
case machineLocal: case machineLocal:
@ -1053,9 +1097,31 @@ func printAPIForwardInstructions(forwardState apiForwardingState, forwardSock st
default: default:
stillString = "" stillString = ""
} }
fmt.Printf("You can %sconnect Docker API clients by setting DOCKER HOST using the\n", stillString)
fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString)
fmt.Printf("following command in your terminal session:\n") fmt.Printf("following command in your terminal session:\n")
fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock) fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock)
} }
} }
} }
func (v *MachineVM) writeConfig() error {
// GetConfDir creates the directory so no need to check for
// its existence
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return err
}
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
// Write the JSON file
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
return err
}
return nil
}

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package wsl package wsl
@ -8,6 +9,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/url"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -35,9 +37,6 @@ const (
ErrorSuccessRebootRequired = 3010 ErrorSuccessRebootRequired = 3010
) )
// Usermode networking avoids potential nftables compatibility issues between the distro
// and the WSL Kernel. Additionally it avoids fw rule conflicts between distros, since
// all instances run under the same Kernel at runtime
const containersConf = `[containers] const containersConf = `[containers]
[engine] [engine]
@ -162,6 +161,8 @@ type MachineVM struct {
Port int Port int
// RemoteUsername of the vm user // RemoteUsername of the vm user
RemoteUsername string RemoteUsername string
// Whether this machine should run in a rootful or rootless manner
Rootful bool
} }
type ExitCodeError struct { type ExitCodeError struct {
@ -227,12 +228,13 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
homeDir := homedir.Get() homeDir := homedir.Get()
sshDir := filepath.Join(homeDir, ".ssh") sshDir := filepath.Join(homeDir, ".ssh")
v.IdentityPath = filepath.Join(sshDir, v.Name) v.IdentityPath = filepath.Join(sshDir, v.Name)
v.Rootful = opts.Rootful
if err := downloadDistro(v, opts); err != nil { if err := downloadDistro(v, opts); err != nil {
return false, err return false, err
} }
if err := writeJSON(v); err != nil { if err := v.writeConfig(); err != nil {
return false, err return false, err
} }
@ -282,7 +284,7 @@ func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
return machine.DownloadImage(dd) return machine.DownloadImage(dd)
} }
func writeJSON(v *MachineVM) error { func (v *MachineVM) writeConfig() error {
vmConfigDir, err := machine.GetConfDir(vmtype) vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil { if err != nil {
return err return err
@ -302,14 +304,26 @@ func writeJSON(v *MachineVM) error {
} }
func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) error { func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) error {
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root") uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil { identity := filepath.Join(sshDir, v.Name)
return err
uris := []url.URL{uri, uriRoot}
names := []string{v.Name, v.Name + "-root"}
// The first connection defined when connections is empty will become the default
// regardless of IsDefault, so order according to rootful
if opts.Rootful {
uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
} }
user := opts.Username for i := 0; i < 2; i++ {
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", withUser("/run/[USER]/1000/podman/podman.sock", user), strconv.Itoa(v.Port), v.RemoteUsername) if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil {
return machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault) return err
}
}
return nil
} }
func provisionWSLDist(v *MachineVM) (string, error) { func provisionWSLDist(v *MachineVM) (string, error) {
@ -704,6 +718,30 @@ func pipeCmdPassThrough(name string, input string, arg ...string) error {
return cmd.Run() return cmd.Run()
} }
func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
if v.Rootful == opts.Rootful {
return nil
}
changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
if err != nil {
return err
}
if changeCon {
newDefault := v.Name
if opts.Rootful {
newDefault += "-root"
}
if err := machine.ChangeDefault(newDefault); err != nil {
return err
}
}
v.Rootful = opts.Rootful
return v.writeConfig()
}
func (v *MachineVM) Start(name string, _ machine.StartOptions) error { func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if v.isRunning() { if v.isRunning() {
return errors.Errorf("%q is already running", name) return errors.Errorf("%q is already running", name)
@ -716,6 +754,18 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return errors.Wrap(err, "WSL bootstrap script failed") return errors.Wrap(err, "WSL bootstrap script failed")
} }
if !v.Rootful {
fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
suffix := ""
if name != machine.DefaultMachineName {
suffix = " " + name
}
fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
}
globalName, pipeName, err := launchWinProxy(v) globalName, pipeName, err := launchWinProxy(v)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.") fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.")