Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
submodules: true
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: stable
Expand All @@ -600,6 +601,8 @@ jobs:
run: brew uninstall --ignore-dependencies --force qemu
- name: Test
run: ./hack/test-templates.sh templates/${{ matrix.template }}
- name: Run BATS VZ block-device tests
run: ./hack/bats/lib/bats-core/bin/bats --timing ./hack/bats/extras/vz-block-device.bats
- if: failure()
uses: ./.github/actions/upload_failure_logs_if_exists
with:
Expand Down
20 changes: 20 additions & 0 deletions cmd/limactl/editflags/editflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func RegisterEdit(cmd *cobra.Command, commentPrefix string) {
flags.Bool("nested-virt", false, commentPrefix+"Enable nested virtualization")

flags.Bool("rosetta", false, commentPrefix+"Enable Rosetta (for vz instances)")
flags.StringSlice("block-device", nil, commentPrefix+"Host block devices to attach on supported backends, e.g. /dev/disk4")

flags.StringArray("set", []string{}, commentPrefix+"Modify the template inplace, using yq syntax. Can be passed multiple times. See 'limactl help yq-restrictions' for limitations.")
flags.StringArray("param", []string{}, commentPrefix+"Set a template parameter, e.g. name=value. Can be passed multiple times.")
Expand Down Expand Up @@ -348,6 +349,25 @@ func YQExpressions(flags *flag.FlagSet, newInstance bool, params map[string]stri
false,
false,
},
{
"block-device",
func(_ *flag.Flag) ([]string, error) {
paths, err := flags.GetStringSlice("block-device")
if err != nil {
return nil, err
}
devices := make([]string, len(paths))
for i, path := range paths {
if path == "" {
return nil, errors.New("block device path must not be empty")
}
devices[i] = strconv.Quote(path)
}
return []string{fmt.Sprintf(".blockDevices += [%s] | .blockDevices |= unique", strings.Join(devices, ","))}, nil
},
false,
false,
},
{"set", func(v *flag.Flag) ([]string, error) {
return v.Value.(flag.SliceValue).GetSlice(), nil
}, false, false},
Expand Down
6 changes: 6 additions & 0 deletions cmd/limactl/editflags/editflags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ func TestYQExpressions(t *testing.T) {
newInstance: false,
expectError: "template does not define param `missing`",
},
{
name: "block-device",
args: []string{"--block-device", "/dev/disk4", "--block-device", "/dev/rdisk5"},
newInstance: false,
expected: []string{`.blockDevices += ["/dev/disk4","/dev/rdisk5"] | .blockDevices |= unique`},
},
{
name: "invalid network",
args: []string{"--network", "invalid"},
Expand Down
6 changes: 5 additions & 1 deletion cmd/limactl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const (
)

func main() {
if os.Geteuid() == 0 && (len(os.Args) < 2 || os.Args[1] != "generate-doc") {
if os.Geteuid() == 0 && (len(os.Args) < 2 || (os.Args[1] != "generate-doc" && !isPrivilegedHelperCommand(os.Args[1]))) {
fmt.Fprint(os.Stderr, "limactl: must not run as the root user\n")
os.Exit(1)
}
Expand Down Expand Up @@ -136,6 +136,9 @@ func newApp() *cobra.Command {
// allow commands that are used for packaging to run under rosetta to allow cross-architecture builds
return errors.New("limactl is running under rosetta, please reinstall lima with native arch")
}
if isPrivilegedHelperCommand(cmd.Name()) {
return nil
}

// Make sure either $HOME or $LIMA_HOME is defined, so we don't need
// to check for errors later
Expand Down Expand Up @@ -212,6 +215,7 @@ func newApp() *cobra.Command {
newWatchCommand(),
newYQRestrictionsHelpCommand(),
)
registerHiddenCommands(rootCmd)
addPluginCommands(rootCmd)

return rootCmd
Expand Down
28 changes: 28 additions & 0 deletions cmd/limactl/sudo_open_block_device_command_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//go:build darwin

// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"github.com/spf13/cobra"

"github.com/lima-vm/lima/v2/pkg/blockdevice"
)

func isPrivilegedHelperCommand(name string) bool {
return name == blockdevice.SudoOpenBlockDeviceCommand
}

func registerHiddenCommands(rootCmd *cobra.Command) {
rootCmd.AddCommand(&cobra.Command{
Use: blockdevice.SudoOpenBlockDeviceCommand,
Short: "Open a host block device via privileged helper",
Args: WrapArgsError(cobra.NoArgs),
Hidden: true,
RunE: func(cmd *cobra.Command, _ []string) error {
return blockdevice.ServeSudoOpenBlockDevice(cmd.InOrStdin())
},
})
}
14 changes: 14 additions & 0 deletions cmd/limactl/sudo_open_block_device_command_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build !darwin

// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package main

import "github.com/spf13/cobra"

func isPrivilegedHelperCommand(string) bool {
return false
}

func registerHiddenCommands(_ *cobra.Command) {}
3 changes: 2 additions & 1 deletion cmd/limactl/sudoers.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ To validate the existing /etc/sudoers.d/lima file:
$ limactl sudoers --check /etc/sudoers.d/lima
`,
Short: "Generate the content of the /etc/sudoers.d/lima file",
Long: fmt.Sprintf(`Generate the content of the /etc/sudoers.d/lima file for enabling vmnet.framework support (socket_vmnet) on macOS.
Long: fmt.Sprintf(`Generate the content of the /etc/sudoers.d/lima file for macOS host helpers that require privilege escalation.
This includes vmnet.framework support (socket_vmnet) and host block-device attachment with --block-device on supported backends.
The content is written to stdout, NOT to the file.
This command must not run as the root user.
See %s for the usage.`, socketVMNetURL),
Expand Down
49 changes: 40 additions & 9 deletions cmd/limactl/sudoers_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import (
"errors"
"fmt"
"io"
"os"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/lima-vm/lima/v2/pkg/blockdevice"
"github.com/lima-vm/lima/v2/pkg/networks"
"github.com/lima-vm/lima/v2/pkg/sudoers"
)

func sudoersAction(cmd *cobra.Command, args []string) error {
Expand All @@ -21,11 +23,6 @@ func sudoersAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
// Make sure the current network configuration is secure
if err := nwCfg.Validate(); err != nil {
logrus.Infof("Please check %s for more information.", socketVMNetURL)
return err
}
check, err := cmd.Flags().GetBool("check")
if err != nil {
return err
Expand All @@ -41,11 +38,16 @@ func sudoersAction(cmd *cobra.Command, args []string) error {
default:
return fmt.Errorf("unexpected arguments %v", args)
}
sudoers, err := networks.Sudoers()
networkSudoers, err := nwCfg.Sudoers()
if err != nil {
return err
}
blockDeviceSudoers, err := blockdevice.Sudoers(nwCfg.Group)
if err != nil {
return err
}
fmt.Fprint(cmd.OutOrStdout(), sudoers)
content := sudoers.AssembleSudoersFragments(networkSudoers, blockDeviceSudoers)
fmt.Fprint(cmd.OutOrStdout(), content)
return nil
}

Expand All @@ -63,9 +65,38 @@ func verifySudoAccess(ctx context.Context, nwCfg networks.Config, args []string,
default:
return errors.New("can check only a single sudoers file")
}
if err := nwCfg.VerifySudoAccess(ctx, file); err != nil {
if err := verifySudoersFile(ctx, nwCfg, file); err != nil {
return err
}
fmt.Fprintf(stdout, "%#q is up-to-date (or sudo doesn't require a password)\n", file)
return nil
}

func verifySudoersFile(ctx context.Context, nwCfg networks.Config, file string) error {
hint := fmt.Sprintf("run `%s sudoers >etc_sudoers.d_lima && sudo install -o root etc_sudoers.d_lima %q`)",
os.Args[0], file)
b, err := os.ReadFile(file)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
if err := nwCfg.VerifySudoAccess(ctx, ""); err == nil {
if err := sudoers.Run(ctx, "root", "wheel", nil, nil, nil, "", "true"); err == nil {
return nil
}
}
}
return fmt.Errorf("can't read %q: %w: (Hint: %s)", file, err, hint)
}
networkSudoers, err := nwCfg.Sudoers()
if err != nil {
return err
}
blockDeviceSudoers, err := blockdevice.Sudoers(nwCfg.Group)
if err != nil {
return err
}
content := sudoers.AssembleSudoersFragments(networkSudoers, blockDeviceSudoers)
if string(b) != content {
return fmt.Errorf("sudoers file %q is out of sync and must be regenerated (Hint: %s)", file, hint)
}
return nil
}
Loading
Loading