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 (
initOpts = machine.InitOptions{}
defaultMachineName = "podman-machine-default"
defaultMachineName = machine.DefaultMachineName
now bool
)
@ -99,6 +99,9 @@ func init() {
IgnitionPathFlagName := "ignition-path"
flags.StringVar(&initOpts.IgnitionPath, IgnitionPathFlagName, "", "Path to ignition file")
_ = 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?

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.
#### **--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**
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 myvm
$ podman machine init --rootful
$ podman machine init --disk-size 50
$ podman machine init --memory=1024 myvm
$ 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 |
| 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 |
| 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 |
| 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 |

View File

@ -27,6 +27,7 @@ type InitOptions struct {
URI url.URL
Username string
ReExec bool
Rootful bool
}
type QemuMachineStatus = string
@ -36,6 +37,7 @@ const (
Running QemuMachineStatus = "running"
// Stopped indicates the vm has stopped
Stopped QemuMachineStatus = "stopped"
DefaultMachineName string = "podman-machine-default"
)
type Provider interface {
@ -89,6 +91,10 @@ type ListResponse struct {
IdentityPath string
}
type SetOptions struct {
Rootful bool
}
type SSHOptions struct {
Username string
Args []string
@ -107,6 +113,7 @@ type RemoveOptions struct {
type VM interface {
Init(opts InitOptions) (bool, error)
Remove(name string, opts RemoveOptions) (string, func() error, error)
Set(name string, opts SetOptions) error
SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) 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()
}
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 {
cfg, err := config.ReadCustomConfig()
if err != nil {

View File

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

View File

@ -5,14 +5,15 @@ package qemu
import (
"bufio"
"encoding/base64"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/fs"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
@ -166,14 +167,8 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
key string
)
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.Rootful = opts.Rootful
switch opts.ImagePath {
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
if len(opts.IgnitionPath) < 1 {
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 {
return false, err
uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
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")
if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
for i := 0; i < 2; i++ {
if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil {
return false, err
}
}
} else {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
}
// Write the JSON file
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
return false, err
}
if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
return false, err
}
v.writeConfig()
// User has provided ignition file so keygen
// will be skipped.
if len(opts.IgnitionPath) < 1 {
var err error
key, err = machine.CreateSSHKeys(v.IdentityPath)
if err != nil {
return false, err
@ -325,6 +324,30 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
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
func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
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
}
@ -929,9 +952,17 @@ func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwa
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-dest", "/run/podman/podman.sock"}...)
cmd = append(cmd, []string{"-forward-user", "root"}...)
cmd = append(cmd, []string{"-forward-dest", destSock}...)
cmd = append(cmd, []string{"-forward-user", forwardUser}...)
cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...)
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 {
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)
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 {
stillString := "still "
switch forwardState {
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 {
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)
}
case machineLocal:
@ -1053,9 +1097,31 @@ func printAPIForwardInstructions(forwardState apiForwardingState, forwardSock st
default:
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("\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
package wsl
@ -8,6 +9,7 @@ import (
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
@ -35,9 +37,6 @@ const (
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]
[engine]
@ -162,6 +161,8 @@ type MachineVM struct {
Port int
// RemoteUsername of the vm user
RemoteUsername string
// Whether this machine should run in a rootful or rootless manner
Rootful bool
}
type ExitCodeError struct {
@ -227,12 +228,13 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
homeDir := homedir.Get()
sshDir := filepath.Join(homeDir, ".ssh")
v.IdentityPath = filepath.Join(sshDir, v.Name)
v.Rootful = opts.Rootful
if err := downloadDistro(v, opts); err != nil {
return false, err
}
if err := writeJSON(v); err != nil {
if err := v.writeConfig(); err != nil {
return false, err
}
@ -282,7 +284,7 @@ func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
return machine.DownloadImage(dd)
}
func writeJSON(v *MachineVM) error {
func (v *MachineVM) writeConfig() error {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return err
@ -302,14 +304,26 @@ func writeJSON(v *MachineVM) 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")
if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
return 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]
}
user := opts.Username
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", withUser("/run/[USER]/1000/podman/podman.sock", user), strconv.Itoa(v.Port), v.RemoteUsername)
return machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault)
for i := 0; i < 2; i++ {
if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil {
return err
}
}
return nil
}
func provisionWSLDist(v *MachineVM) (string, error) {
@ -704,6 +718,30 @@ func pipeCmdPassThrough(name string, input string, arg ...string) error {
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 {
if v.isRunning() {
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")
}
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)
if err != nil {
fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.")