diff --git a/cmd/crc/cmd/config/config_linux.go b/cmd/crc/cmd/config/config_linux.go new file mode 100644 index 0000000000..157360c227 --- /dev/null +++ b/cmd/crc/cmd/config/config_linux.go @@ -0,0 +1,43 @@ +package config + +import ( + cfg "github.com/code-ready/crc/pkg/crc/config" +) + +var ( + // Preflight checks + SkipCheckRootUser = cfg.AddSetting("skip-check-root-user", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckRootUser = cfg.AddSetting("warn-check-root-user", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckVirtEnabled = cfg.AddSetting("skip-check-virt-enabled", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckVirtEnabled = cfg.AddSetting("warn-check-virt-enabled", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckKvmEnabled = cfg.AddSetting("skip-check-kvm-enabled", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckKvmEnabled = cfg.AddSetting("warn-check-kvm-enabled", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckLibvirtInstalled = cfg.AddSetting("skip-check-libvirt-installed", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckLibvirtInstalled = cfg.AddSetting("warn-check-libvirt-installed", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckLibvirtEnabled = cfg.AddSetting("skip-check-libvirt-enabled", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckLibvirtEnabled = cfg.AddSetting("warn-check-libvirt-enabled", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckLibvirtRunning = cfg.AddSetting("skip-check-libvirt-running", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckLibvirtVersionCheck = cfg.AddSetting("warn-check-libvirt-version", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckLibvirtVersionCheck = cfg.AddSetting("skip-check-libvirt-version", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckLibvirtRunning = cfg.AddSetting("warn-check-libvirt-running", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckUserInLibvirtGroup = cfg.AddSetting("skip-check-user-in-libvirt-group", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckUserInLibvirtGroup = cfg.AddSetting("warn-check-user-in-libvirt-group", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckLibvirtDriver = cfg.AddSetting("skip-check-libvirt-driver", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckLibvirtDriver = cfg.AddSetting("warn-check-libvirt-driver", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckCrcNetwork = cfg.AddSetting("skip-check-crc-network", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckCrcNetwork = cfg.AddSetting("warn-check-crc-network", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckCrcNetworkActive = cfg.AddSetting("skip-check-crc-network-active", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckCrcNetworkActive = cfg.AddSetting("warn-check-crc-network-active", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckCrcBridgePermissions = cfg.AddSetting("skip-check-crc-network-permissions", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckCrcBridgePermissions = cfg.AddSetting("warn-check-crc-network-permissions", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckCrcDnsmasqFile = cfg.AddSetting("skip-check-crc-dnsmasq-file", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckCrcDnsmasqFile = cfg.AddSetting("warn-check-crc-dnsmasq-file", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckCrcNetworkManagerConfig = cfg.AddSetting("skip-check-network-manager-config", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckCrcNetworkManagerConfig = cfg.AddSetting("warn-check-network-manager-config", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckNetworkManagerInstalled = cfg.AddSetting("warn-check-network-manager-installed", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckNetworkManagerInstalled = cfg.AddSetting("skip-check-network-manager-installed", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckNetworkManagerRunning = cfg.AddSetting("warn-check-network-manager-running", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckNetworkManagerRunning = cfg.AddSetting("skip-check-network-manager-running", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + WarnCheckSystemLibvirtVM = cfg.AddSetting("warn-check-system-libvirt-vm", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) + SkipCheckSystemLibvirtVM = cfg.AddSetting("skip-check-system-libvirt-vm", nil, []cfg.ValidationFnType{cfg.ValidateBool}, []cfg.SetFn{cfg.SuccessfullyApplied}) +) diff --git a/pkg/crc/machine/libvirt/templates_linux.go b/pkg/crc/machine/libvirt/templates_linux.go index 4e5522517b..09d95f197b 100644 --- a/pkg/crc/machine/libvirt/templates_linux.go +++ b/pkg/crc/machine/libvirt/templates_linux.go @@ -9,7 +9,7 @@ const ( - + diff --git a/pkg/crc/preflight/preflight_checks_linux.go b/pkg/crc/preflight/preflight_checks_linux.go index e97bd4157c..43baf85f68 100644 --- a/pkg/crc/preflight/preflight_checks_linux.go +++ b/pkg/crc/preflight/preflight_checks_linux.go @@ -41,6 +41,11 @@ server=/crc.testing/192.168.130.11 crcNetworkManagerConfig = `[main] dns=dnsmasq ` + qemuBridgeConfig = "allow crc" + qemuBridgeConfigPaths = []string{ + "/etc/qemu/bridge.conf", // Upstream + "/etc/qemu-kvm/bridge.conf", // RHEL + } ) func checkVirtualizationEnabled() error { @@ -392,6 +397,44 @@ func fixLibvirtCrcNetworkActive() error { return nil } +func checkLibvirtCrcBridgePermissions() error { + logging.Debug("Checking if 'crc' bridge has appropriate permissions setup") + configPath, err := crcos.GetFirstExistentPath(qemuBridgeConfigPaths[:]) + if err != nil { + return fmt.Errorf("Failed to find Qemu bridge configuration file: %s", err) + } + + config, err := ioutil.ReadFile(filepath.Clean(configPath)) + if err != nil { + return fmt.Errorf("Failed to read %s: %v", configPath, err) + } + regex := regexp.MustCompile(fmt.Sprintf("(?m)(\n|^)[[:space:]]*%s[[:space:]]*(\n|$)", qemuBridgeConfig)) + if !regex.Match(config) { + return fmt.Errorf("Unpriviledged access to crc network is not allowed") + } + logging.Debug("The 'crc' bridge can be used by qemu-bridge-helper/session libvirt") + return nil +} + +func fixLibvirtCrcBridgePermissions() error { + logging.Debug("Fixing permissions for 'crc'") + configPath, err := crcos.GetFirstExistentPath(qemuBridgeConfigPaths[:]) + if err != nil { + return fmt.Errorf("Failed to find Qemu bridge configuration file: %s", err) + } + + err = crcos.AppendToFileAsRoot( + "Allow 'crc' network to be used from session libvirt", + fmt.Sprintf("%s\n", qemuBridgeConfig), + configPath, + ) + if err != nil { + return fmt.Errorf("Failed to write to %s: %v", configPath, err) + } + logging.Debug("The 'crc' bridge can now be used by qemu-bridge-helper/session libvirt") + return nil +} + func checkCrcDnsmasqConfigFile() error { logging.Debug("Checking dnsmasq configuration") c := []byte(crcDnsmasqConfig) @@ -504,3 +547,50 @@ func CheckNetworkManagerIsRunning() error { func fixNetworkManagerIsRunning() error { return fmt.Errorf("NetworkManager is required. Please make sure it is installed and running manually") } + +func checkSystemLibvirtVM() error { + logging.Debug("Checking for existing VM on libvirt's system connection") + stdOut, stdErr, err := crcos.RunWithDefaultLocale("virsh", "--connect", "qemu:///system", "list", "--all") + if err != nil { + return fmt.Errorf("%+v: %s", err, stdErr) + } + regex := regexp.MustCompile(`- +crc`) + + outputSlice := strings.Split(stdOut, "\n") + for _, stdOut = range outputSlice { + if regex.Match([]byte(stdOut)) { + return errors.New("Existing VM on system libvirt connection") + } + } + logging.Debug("No existing existing VM on libvirt system connection") + return nil +} + +func fixSystemLibvirtVM() error { + logging.Debug("Deleting existing VM from libvirt's system connection") + _, stdErr, err := crcos.RunWithDefaultLocale("virsh", "--connect", "qemu:///system", "delete", "crc") + if err != nil { + // VM is probably not running, that's OK. + logging.Debug("Failed to stop `crc` VM: %+v: %s", err, stdErr) + } + _, stdErr, err = crcos.RunWithDefaultLocale("virsh", "--connect", "qemu:///system", "undefine", "crc") + if err != nil { + return fmt.Errorf("%+v: %s", err, stdErr) + } + + // Ensure disk is owned by current user + + // FIXME: First check if owner isn't already correctly set, even though if this function is called, it likely means + // it's not. + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("Error fetching current user info: %+v: %s", err, stdErr) + } + diskPath := filepath.Join(constants.MachineBaseDir, "machines", constants.DefaultName, constants.DefaultName) + err = crcos.ChownAsRoot(currentUser, diskPath) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/crc/preflight/preflight_linux.go b/pkg/crc/preflight/preflight_linux.go index aaa08f676f..9900022c13 100644 --- a/pkg/crc/preflight/preflight_linux.go +++ b/pkg/crc/preflight/preflight_linux.go @@ -80,6 +80,13 @@ var libvirtPreflightChecks = [...]PreflightCheck{ fixDescription: "Starting libvirt 'crc' network", fix: fixLibvirtCrcNetworkActive, }, + { + configKeySuffix: "check-crc-network-permission", + checkDescription: "Checking for appropriate permissions on 'crc' network", + check: checkLibvirtCrcBridgePermissions, + fixDescription: "Setting appropriate permissions on 'crc' network", + fix: fixLibvirtCrcBridgePermissions, + }, { configKeySuffix: "check-network-manager-installed", checkDescription: "Checking if NetworkManager is installed", @@ -108,6 +115,13 @@ var libvirtPreflightChecks = [...]PreflightCheck{ fixDescription: "Writing dnsmasq config for crc", fix: fixCrcDnsmasqConfigFile, }, + { + configKeySuffix: "check-system-libvirt-vm", + checkDescription: "Checking for existing `crc` VM on system libvirt connection", + check: checkSystemLibvirtVM, + fixDescription: "Importing existing `crc` VM", + fix: fixSystemLibvirtVM, + }, } func getPreflightChecks() []PreflightCheck { diff --git a/pkg/os/util_linux.go b/pkg/os/util_linux.go index 2da1dda191..33c392ccbf 100644 --- a/pkg/os/util_linux.go +++ b/pkg/os/util_linux.go @@ -3,15 +3,29 @@ package os import ( "bytes" "fmt" + "os" "os/exec" + "os/user" "strings" "github.com/code-ready/crc/pkg/crc/logging" ) func WriteToFileAsRoot(reason, content, filepath string) error { + return writeToFileAsRoot(reason, content, filepath, false) +} + +func AppendToFileAsRoot(reason, content, filepath string) error { + return writeToFileAsRoot(reason, content, filepath, true) +} + +func writeToFileAsRoot(reason, content, filepath string, append bool) error { logging.Infof("Will use root access: %s", reason) - cmd := exec.Command("sudo", "tee", filepath) // #nosec G204 + append_option := "" + if append { + append_option = "-a" + } + cmd := exec.Command("sudo", "tee", append_option, filepath) // #nosec G204 cmd.Stdin = strings.NewReader(content) buf := new(bytes.Buffer) cmd.Stderr = buf @@ -20,3 +34,34 @@ func WriteToFileAsRoot(reason, content, filepath string) error { } return nil } + +func GetFirstExistentPath(paths []string) (string, error) { + readablePath := "" + for _, path := range paths { + logging.Debug(fmt.Sprintf("Trying %s..", path)) + _, err := os.Stat(path) + if err != nil { + logging.Debug(fmt.Sprintf("Failed to open %s: %s", path, err)) + } else { + readablePath = path + + break + } + } + if readablePath == "" { + return "", fmt.Errorf("Failed to find a readable file on any of these paths: %s", strings.Join(paths, ", ")) + } + + return readablePath, nil +} + +func ChownAsRoot(user *user.User, filepath string) error { + logging.Infof("Will use root access to change owner & group of file %s to %s", filepath, user.Username) + cmd := exec.Command("sudo", "chown", fmt.Sprintf("%s.%s", user.Uid, user.Gid), filepath) // #nosec G204 + buf := new(bytes.Buffer) + cmd.Stderr = buf + if err := cmd.Run(); err != nil { + return fmt.Errorf("Failed to change owner & group of %s to %s: %s: %s: %v", filepath, user.Username, buf.String(), err) + } + return nil +}