Repository: code-ready/crc
Pull request #2102 (2021-03-15 14:47:05)

#2004 Extract then rename can be too fast for Windows antivirus
Add a retry loop as a temporary fix.

In the future we should this change to:
* read the first bytes of the bundle and get the manifest,
* find the bundle name and check if this bundle already exists,
* extract the bundle directly in the right directory, skipping the
directory structure of the archive.

Related to #2004

openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/code-ready/crc/pull/2102#" title="Author self-approved">guillaumerose</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=code-ready%2Fcrc).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/code-ready/crc/blob/master/OWNERS)~~ [guillaumerose]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/code-ready/crc/pull/2102#pullrequestreview-612765103" title="Approved">gbraad</a>*, *<a href="https://github.com/code-ready/crc/pull/2102#" title="Author self-approved">guillaumerose</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=code-ready%2Fcrc).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/code-ready/crc/blob/master/OWNERS)~~ [gbraad,guillaumerose]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
gbraad: > Were you able to reproduce? Not really clear why being too fast is an issue?

Not myself as I do not really on 3rd party tools. But this kinda a known thing.

Patch:
diff --git a/Makefile b/Makefile
index 3ea219db7..c08272988 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 SHELL := /bin/bash
 
-BUNDLE_VERSION = 4.7.1
+BUNDLE_VERSION = 4.7.2
 BUNDLE_EXTENSION = crcbundle
 CRC_VERSION = 1.23.1
 COMMIT_SHA=$(shell git rev-parse --short HEAD)
diff --git a/centos_ci.sh b/centos_ci.sh
index 316897b4d..e5682be2f 100644
--- a/centos_ci.sh
+++ b/centos_ci.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 # bundle location
-BUNDLE_VERSION=4.7.1
+BUNDLE_VERSION=4.7.2
 BUNDLE=crc_libvirt_$BUNDLE_VERSION.crcbundle
 GO_VERSION=1.14.13
 
diff --git a/cmd/crc/cmd/config/config.go b/cmd/crc/cmd/config/config.go
index b27cc2e5e..317ec9d2f 100644
--- a/cmd/crc/cmd/config/config.go
+++ b/cmd/crc/cmd/config/config.go
@@ -3,6 +3,7 @@ package config
 import (
 	"bytes"
 	"fmt"
+	"runtime"
 	"sort"
 	"strings"
 	"text/tabwriter"
@@ -36,6 +37,16 @@ const (
 )
 
 func RegisterSettings(cfg *config.Config) {
+	validateTrayAutostart := func(value interface{}) (bool, string) {
+		if runtime.GOOS != "darwin" {
+			return false, "Tray autostart is only supported on macOS"
+		}
+		if !cfg.Get(ExperimentalFeatures).AsBool() {
+			return false, fmt.Sprintf("'%s' must be enabled in order to use the tray", ExperimentalFeatures)
+		}
+		return config.ValidateBool(value)
+	}
+
 	validateHostNetworkAccess := func(value interface{}) (bool, string) {
 		mode := network.ParseMode(cfg.Get(NetworkMode).AsString())
 		if mode != network.VSockMode {
@@ -84,7 +95,7 @@ func RegisterSettings(cfg *config.Config) {
 	cfg.AddSetting(HostNetworkAccess, false, validateHostNetworkAccess, config.SuccessfullyApplied,
 		"Allow TCP/IP connections from the CodeReady Containers VM to services running on the host (true/false, default: false)")
 	// System tray auto-start config
-	cfg.AddSetting(AutostartTray, true, tray.ValidateTrayAutostart, disableEnableTrayAutostart,
+	cfg.AddSetting(AutostartTray, true, validateTrayAutostart, disableEnableTrayAutostart,
 		"Automatically start the tray (true/false, default: true)")
 	// Proxy Configuration
 	cfg.AddSetting(HTTPProxy, "", config.ValidateURI, config.SuccessfullyApplied,
diff --git a/cmd/crc/cmd/daemon.go b/cmd/crc/cmd/daemon.go
index b7b15dbcb..fec7cd2a3 100644
--- a/cmd/crc/cmd/daemon.go
+++ b/cmd/crc/cmd/daemon.go
@@ -14,6 +14,7 @@ import (
 	cmdConfig "github.com/code-ready/crc/cmd/crc/cmd/config"
 	"github.com/code-ready/crc/pkg/crc/api"
 	"github.com/code-ready/crc/pkg/crc/constants"
+	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/code-ready/gvisor-tap-vsock/pkg/types"
 	"github.com/code-ready/gvisor-tap-vsock/pkg/virtualnetwork"
 	"github.com/docker/go-units"
@@ -121,6 +122,7 @@ func run(configuration *types.Configuration) error {
 		}
 		mux := http.NewServeMux()
 		mux.Handle("/network/", http.StripPrefix("/network", vn.Mux()))
+		mux.Handle("/api/", http.StripPrefix("/api", api.NewMux(config, newMachine())))
 		if err := http.Serve(listener, mux); err != nil {
 			errCh <- err
 		}
@@ -162,7 +164,7 @@ func run(configuration *types.Configuration) error {
 func runDaemon() error {
 	// Remove if an old socket is present
 	os.Remove(constants.DaemonSocketPath)
-	apiServer, err := api.CreateServer(constants.DaemonSocketPath, config, newMachine())
+	apiServer, err := api.CreateServer(constants.DaemonSocketPath, config, newMachine(), logging.Memory)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/crc/cmd/delete.go b/cmd/crc/cmd/delete.go
index 01a31ea31..80dba55e7 100644
--- a/cmd/crc/cmd/delete.go
+++ b/cmd/crc/cmd/delete.go
@@ -9,6 +9,7 @@ import (
 	"github.com/code-ready/crc/pkg/crc/constants"
 	crcErrors "github.com/code-ready/crc/pkg/crc/errors"
 	"github.com/code-ready/crc/pkg/crc/input"
+	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/code-ready/crc/pkg/crc/machine"
 	"github.com/spf13/cobra"
 )
@@ -53,7 +54,10 @@ func deleteMachine(client machine.Client, clearCache bool, cacheDir string, inte
 
 	yes := input.PromptUserForYesOrNo("Do you want to delete the OpenShift cluster", force)
 	if yes {
-		return true, client.Delete()
+		if err := client.Delete(); err != nil {
+			return true, err
+		}
+		return true, logging.BackupLogFile()
 	}
 	return false, nil
 }
diff --git a/docs/source/meta/product-attributes.adoc b/docs/source/meta/product-attributes.adoc
index 4aa3b7356..77db6ab14 100644
--- a/docs/source/meta/product-attributes.adoc
+++ b/docs/source/meta/product-attributes.adoc
@@ -36,3 +36,5 @@
 :odo-docs-url: https://docs.openshift.com/container-platform/latest/cli_reference/developer_cli_odo/understanding-odo.html
 :odo-docs-url-installing: https://docs.openshift.com/container-platform/latest/cli_reference/developer_cli_odo/installing-odo.html
 :odo-docs-url-single-component: https://docs.openshift.com/container-platform/latest/cli_reference/developer_cli_odo/creating_and_deploying_applications_with_odo/creating-a-single-component-application-with-odo.html
+
+:rhel-resolved-docs: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/using-different-dns-servers-for-different-domains_configuring-and-managing-networking
diff --git a/docs/source/topics/proc_accessing-the-openshift-cluster-with-oc.adoc b/docs/source/topics/proc_accessing-the-openshift-cluster-with-oc.adoc
index 2d08ad406..67a3b4334 100644
--- a/docs/source/topics/proc_accessing-the-openshift-cluster-with-oc.adoc
+++ b/docs/source/topics/proc_accessing-the-openshift-cluster-with-oc.adoc
@@ -33,11 +33,15 @@ You can also view it by running the [command]`{bin} console --credentials` comma
 ====
 
 . You can now use [command]`oc` to interact with your OpenShift cluster.
-For example, to verify that the OpenShift cluster Operators are available, run the following command with administrator privileges:
+For example, to verify that the OpenShift cluster Operators are available, you need to be logged in as `kubeadmin` user and
+run the following command:
 +
 [subs="+quotes,attributes",options="nowrap"]
 ----
-$ oc get co --as system:admin
+$ oc config use-context crc-admin
+$ oc whoami
+kubeadmin
+$ oc get co
 ----
 +
 [NOTE]
diff --git a/docs/source/topics/ref_dns-configuration.adoc b/docs/source/topics/ref_dns-configuration.adoc
index b80b654f8..1ff4d1c5e 100644
--- a/docs/source/topics/ref_dns-configuration.adoc
+++ b/docs/source/topics/ref_dns-configuration.adoc
@@ -29,6 +29,7 @@ This configuration is used on Fedora 33 or newer, and on Ubuntu Desktop editions
 * `systemd-resolved` configuration is done through a NetworkManager dispatcher script in `/etc/NetworkManager/dispatcher.d/99-crc.sh`:
 +
 ----
+#!/bin/sh
 resolvectl domain crc ~testing
 resolvectl dns crc 192.168.130.11
 resolvectl default-route crc false
@@ -36,6 +37,19 @@ resolvectl default-route crc false
 exit 0
 ----
 
+[NOTE]
+====
+`systemd-resolved` is also available as an unsupported Technology Preview on {rhel} and {centos} 8.3. After {rhel-resolved-docs}[configuring the host] to use `systemd-resolved`, any {prod} `dnsmasq` configurations need to be removed and the above script needs to be created with executable permissions.
+
+Configure the {prod} binary to skip startup checks for `dnsmasq` and NetworkManager configuration:
+
+[subs="+quotes,attributes"]
+----
+$ {bin} config set skip-check-crc-dnsmasq-file true
+$ {bin} config set skip-check-network-manager-config true
+----
+====
+
 === NetworkManager + dnsmasq
 
 This configuration is used on Fedora 32 or older, on {rhel}, and on {centos}.
diff --git a/go.mod b/go.mod
index 26cbd0f61..c09f66afd 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/cavaliercoder/grab v2.0.0+incompatible
 	github.com/cheggaaa/pb/v3 v3.0.5
 	github.com/code-ready/clicumber v0.0.0-20210201104241-cecb794bdf9a
-	github.com/code-ready/gvisor-tap-vsock v0.0.0-20210216090540-914d522b9c38
+	github.com/code-ready/gvisor-tap-vsock v0.0.0-20210308122700-d61f9aac135c
 	github.com/code-ready/machine v0.0.0-20210122113819-281ccfbb4566
 	github.com/cucumber/godog v0.9.0
 	github.com/cucumber/messages-go/v10 v10.0.3
@@ -51,7 +51,7 @@ require (
 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
 	github.com/zalando/go-keyring v0.1.1
-	golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c
+	golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
 	golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect
 	golang.org/x/oauth2 v0.0.0-20201203001011-0b49973bad19 // indirect
 	golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
diff --git a/go.sum b/go.sum
index bd13dd7b2..9f3ba956c 100644
--- a/go.sum
+++ b/go.sum
@@ -163,8 +163,8 @@ github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/code-ready/clicumber v0.0.0-20210201104241-cecb794bdf9a h1:VOQmCj9Y8pAPU4C0rIvF2ByUBuuqnaR9JSvb9C8uBQE=
 github.com/code-ready/clicumber v0.0.0-20210201104241-cecb794bdf9a/go.mod h1:ZPZUcYGpv/FQGnwakUuhiHP9x/IIcU2SKbn8xPe4ROc=
-github.com/code-ready/gvisor-tap-vsock v0.0.0-20210216090540-914d522b9c38 h1:vo1A+Tvie+BVZoSyYOzShDH6ieQkD1Q9l4JeeC0jCxA=
-github.com/code-ready/gvisor-tap-vsock v0.0.0-20210216090540-914d522b9c38/go.mod h1:DsNrkk0GUSTqt0E/4/GMvjqsP7ns++O/6YaJn/TDBVM=
+github.com/code-ready/gvisor-tap-vsock v0.0.0-20210308122700-d61f9aac135c h1:UOg6w7Uv5hWvFVABjJpawCEmAK99vG96SozUaulqVCk=
+github.com/code-ready/gvisor-tap-vsock v0.0.0-20210308122700-d61f9aac135c/go.mod h1:DsNrkk0GUSTqt0E/4/GMvjqsP7ns++O/6YaJn/TDBVM=
 github.com/code-ready/machine v0.0.0-20210122113819-281ccfbb4566 h1:zBDinebc2kFq99UNTmLmTRfgxcxC6YieKQuXDRCljzw=
 github.com/code-ready/machine v0.0.0-20210122113819-281ccfbb4566/go.mod h1:4JgMqho7KuxFifYCIi7cKVmh4yA2l0QHOPftv9f5PNc=
 github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
@@ -966,8 +966,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
diff --git a/packaging/darwin/Distribution.in b/packaging/darwin/Distribution.in
index 905ecfbda..26861de12 100755
--- a/packaging/darwin/Distribution.in
+++ b/packaging/darwin/Distribution.in
@@ -10,9 +10,9 @@
     <installation-check script="installCheck();"/>
     <script>
 function installCheck() {
-    if(!(system.compareVersions(system.version.ProductVersion, '10.12.0') >= 0)) {
+    if(!(system.compareVersions(system.version.ProductVersion, '10.14.0') >= 0)) {
         my.result.title = 'Unable to install';
-        my.result.message = 'CodeReady Containers requires macOS 10.12 or later.';
+        my.result.message = 'CodeReady Containers requires macOS 10.14 or later.';
         my.result.type = 'Fatal';
         return false;
     }
diff --git a/pkg/crc/api/adapter.go b/pkg/crc/api/adapter.go
index 00e50cd3c..4abcb41e8 100644
--- a/pkg/crc/api/adapter.go
+++ b/pkg/crc/api/adapter.go
@@ -3,49 +3,19 @@ package api
 import (
 	"context"
 
+	"github.com/code-ready/crc/pkg/crc/api/client"
 	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/code-ready/crc/pkg/crc/machine"
+	"github.com/code-ready/machine/libmachine/state"
 )
 
 type AdaptedClient interface {
 	GetName() string
-
-	Delete() Result
-	GetConsoleURL() ConsoleResult
-	Start(ctx context.Context, startConfig machine.StartConfig) StartResult
-	Status() ClusterStatusResult
-	Stop() Result
-}
-
-type Result struct {
-	Name    string
-	Success bool
-	Error   string
-}
-
-type StartResult struct {
-	Name           string
-	Status         string
-	Error          string
-	ClusterConfig  machine.ClusterConfig
-	KubeletStarted bool
-}
-
-type ClusterStatusResult struct {
-	Name             string
-	CrcStatus        string
-	OpenshiftStatus  string
-	OpenshiftVersion string
-	DiskUse          int64
-	DiskSize         int64
-	Error            string
-	Success          bool
-}
-
-type ConsoleResult struct {
-	ClusterConfig machine.ClusterConfig
-	Success       bool
-	Error         string
+	Delete() client.Result
+	GetConsoleURL() client.ConsoleResult
+	Start(ctx context.Context, startConfig machine.StartConfig) client.StartResult
+	Status() client.ClusterStatusResult
+	Stop() client.Result
 }
 
 type Adapter struct {
@@ -56,47 +26,47 @@ func (a *Adapter) GetName() string {
 	return a.Underlying.GetName()
 }
 
-func (a *Adapter) Delete() Result {
+func (a *Adapter) Delete() client.Result {
 	err := a.Underlying.Delete()
 	if err != nil {
 		logging.Error(err)
-		return Result{
+		return client.Result{
 			Name:    a.Underlying.GetName(),
 			Success: false,
 			Error:   err.Error(),
 		}
 	}
-	return Result{
+	return client.Result{
 		Name:    a.Underlying.GetName(),
 		Success: true,
 	}
 }
 
-func (a *Adapter) GetConsoleURL() ConsoleResult {
+func (a *Adapter) GetConsoleURL() client.ConsoleResult {
 	res, err := a.Underlying.GetConsoleURL()
 	if err != nil {
 		logging.Error(err)
-		return ConsoleResult{
+		return client.ConsoleResult{
 			Success: false,
 			Error:   err.Error(),
 		}
 	}
-	return ConsoleResult{
+	return client.ConsoleResult{
 		ClusterConfig: res.ClusterConfig,
 		Success:       true,
 	}
 }
 
-func (a *Adapter) Start(ctx context.Context, startConfig machine.StartConfig) StartResult {
+func (a *Adapter) Start(ctx context.Context, startConfig machine.StartConfig) client.StartResult {
 	res, err := a.Underlying.Start(ctx, startConfig)
 	if err != nil {
 		logging.Error(err)
-		return StartResult{
+		return client.StartResult{
 			Name:  a.Underlying.GetName(),
 			Error: err.Error(),
 		}
 	}
-	return StartResult{
+	return client.StartResult{
 		Name:           a.Underlying.GetName(),
 		Status:         res.Status.String(),
 		ClusterConfig:  res.ClusterConfig,
@@ -104,17 +74,17 @@ func (a *Adapter) Start(ctx context.Context, startConfig machine.StartConfig) St
 	}
 }
 
-func (a *Adapter) Status() ClusterStatusResult {
+func (a *Adapter) Status() client.ClusterStatusResult {
 	res, err := a.Underlying.Status()
 	if err != nil {
 		logging.Error(err)
-		return ClusterStatusResult{
+		return client.ClusterStatusResult{
 			Name:    a.Underlying.GetName(),
 			Error:   err.Error(),
 			Success: false,
 		}
 	}
-	return ClusterStatusResult{
+	return client.ClusterStatusResult{
 		Name:             a.Underlying.GetName(),
 		CrcStatus:        res.CrcStatus.String(),
 		OpenshiftStatus:  res.OpenshiftStatus,
@@ -125,17 +95,23 @@ func (a *Adapter) Status() ClusterStatusResult {
 	}
 }
 
-func (a *Adapter) Stop() Result {
-	_, err := a.Underlying.Stop()
+func (a *Adapter) Stop() client.Result {
+	vmState, err := a.Underlying.Stop()
 	if err != nil {
 		logging.Error(err)
-		return Result{
-			Name:    a.Underlying.GetName(),
-			Success: false,
-			Error:   err.Error(),
+		if vmState == state.Running {
+			err := a.Underlying.PowerOff()
+			if err != nil {
+				logging.Error(err)
+				return client.Result{
+					Name:    a.Underlying.GetName(),
+					Success: false,
+					Error:   err.Error(),
+				}
+			}
 		}
 	}
-	return Result{
+	return client.Result{
 		Name:    a.Underlying.GetName(),
 		Success: true,
 	}
diff --git a/pkg/crc/api/api.go b/pkg/crc/api/api.go
index 220206e08..7b7953b37 100644
--- a/pkg/crc/api/api.go
+++ b/pkg/crc/api/api.go
@@ -11,22 +11,23 @@ import (
 	"github.com/code-ready/crc/pkg/crc/machine"
 )
 
-func CreateServer(socketPath string, config crcConfig.Storage, machine machine.Client) (Server, error) {
+func CreateServer(socketPath string, config crcConfig.Storage, machine machine.Client, logger Logger) (Server, error) {
 	listener, err := net.Listen("unix", socketPath)
 	if err != nil {
 		logging.Error("Failed to create socket: ", err.Error())
 		return Server{}, err
 	}
-	return createServerWithListener(listener, config, machine)
+	return createServerWithListener(listener, config, machine, logger)
 }
 
-func createServerWithListener(listener net.Listener, config crcConfig.Storage, machine machine.Client) (Server, error) {
+func createServerWithListener(listener net.Listener, config crcConfig.Storage, machine machine.Client, logger Logger) (Server, error) {
 	apiServer := Server{
 		listener:               listener,
 		clusterOpsRequestsChan: make(chan clusterOpsRequest, 10),
 		handler: &Handler{
 			Config:        config,
 			MachineClient: &Adapter{Underlying: machine},
+			Logger:        logger,
 		},
 	}
 	return apiServer, nil
@@ -76,6 +77,8 @@ func (api Server) handleRequest(req commandRequest, conn net.Conn) {
 		result = api.handler.GetConfig(req.Args)
 	case "webconsoleurl":
 		result = api.handler.GetWebconsoleInfo()
+	case "logs":
+		result = api.handler.Logs()
 	default:
 		result = encodeErrorToJSON(fmt.Sprintf("Unknown command supplied: %s", req.Command))
 	}
@@ -114,7 +117,7 @@ func (api Server) handleConnections(conn net.Conn) {
 			conn.Close()
 		}
 
-	case "status", "version", "setconfig", "getconfig", "unsetconfig", "webconsoleurl":
+	case "status", "version", "setconfig", "getconfig", "unsetconfig", "webconsoleurl", "logs":
 		go api.handleRequest(req, conn)
 
 	default:
diff --git a/pkg/crc/api/api_http.go b/pkg/crc/api/api_http.go
new file mode 100644
index 000000000..f0d0eb7e6
--- /dev/null
+++ b/pkg/crc/api/api_http.go
@@ -0,0 +1,150 @@
+package api
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	crcConfig "github.com/code-ready/crc/pkg/crc/config"
+	"github.com/code-ready/crc/pkg/crc/logging"
+	"github.com/code-ready/crc/pkg/crc/machine"
+)
+
+func NewMux(config crcConfig.Storage, machine machine.Client) http.Handler {
+	handler := NewHandler(config, machine)
+
+	mux := http.NewServeMux()
+	mux.HandleFunc("/start", func(w http.ResponseWriter, r *http.Request) {
+		var startResult string
+		data, err := verifyRequestAndReadBody(w, r, http.MethodGet, http.MethodPost)
+		if err != nil {
+			return
+		}
+		if len(data) > 0 {
+			startResult = handler.Start(data)
+		} else {
+			startResult = handler.Start(nil)
+		}
+		sendResponse(w, startResult)
+	})
+
+	mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
+		if wrongHTTPMethodUsed(r, w, http.MethodGet) {
+			return
+		}
+		stopResult := handler.Stop()
+		sendResponse(w, stopResult)
+	})
+
+	mux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
+		if wrongHTTPMethodUsed(r, w, http.MethodGet) {
+			return
+		}
+		status := handler.Status()
+		sendResponse(w, status)
+	})
+
+	mux.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request) {
+		if wrongHTTPMethodUsed(r, w, http.MethodGet) {
+			return
+		}
+		deleteResult := handler.Delete()
+		sendResponse(w, deleteResult)
+	})
+
+	mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
+		if wrongHTTPMethodUsed(r, w, http.MethodGet) {
+			return
+		}
+		versionResult := handler.GetVersion()
+		sendResponse(w, versionResult)
+	})
+
+	mux.HandleFunc("/webconsoleurl", func(w http.ResponseWriter, r *http.Request) {
+		if wrongHTTPMethodUsed(r, w, http.MethodGet) {
+			return
+		}
+		webconsoleInfo := handler.GetWebconsoleInfo()
+		sendResponse(w, webconsoleInfo)
+	})
+
+	mux.Handle("/config/", http.StripPrefix("/config", newConfigMux(handler)))
+
+	return mux
+}
+
+func newConfigMux(handler *Handler) http.Handler {
+	mux := http.NewServeMux()
+
+	mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
+		var configGetResult string
+		data, err := verifyRequestAndReadBody(w, r, http.MethodGet, http.MethodPost)
+		if err != nil {
+			return
+		}
+		if len(data) > 0 {
+			configGetResult = handler.GetConfig(data)
+		} else {
+			configGetResult = handler.GetConfig(nil)
+		}
+		sendResponse(w, configGetResult)
+	})
+
+	mux.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
+		handleConfigSetUnset(w, r, func(data []byte) string { return handler.SetConfig(data) })
+	})
+
+	mux.HandleFunc("/unset", func(w http.ResponseWriter, r *http.Request) {
+		handleConfigSetUnset(w, r, func(data []byte) string { return handler.UnsetConfig(data) })
+	})
+
+	return mux
+}
+
+func wrongHTTPMethodUsed(r *http.Request, w http.ResponseWriter, allowedMethods ...string) bool {
+	for _, method := range allowedMethods {
+		if r.Method == method {
+			return false
+		}
+	}
+	logging.Debugf("Wrong method: %s used for request to: %s", r.Method, r.RequestURI)
+	http.Error(w, fmt.Sprintf("Only %s is allowed.", strings.Join(allowedMethods, ",")), http.StatusMethodNotAllowed)
+	return true
+}
+
+func sendResponse(w http.ResponseWriter, response string) {
+	w.Header().Set("Content-Type", "application/json")
+	if _, err := io.WriteString(w, response); err != nil {
+		logging.Error("Failed to send response: ", err)
+	}
+}
+
+func handleConfigSetUnset(w http.ResponseWriter, r *http.Request, fn func([]byte) string) {
+	data, err := verifyRequestAndReadBody(w, r, http.MethodPost)
+	if err != nil {
+		return
+	}
+	if len(data) < 1 {
+		http.Error(w, "Not enough arguments for /config/(un)set", http.StatusBadRequest)
+		return
+	}
+
+	result := fn(data)
+	sendResponse(w, result)
+}
+
+func verifyRequestAndReadBody(w http.ResponseWriter, r *http.Request, method ...string) ([]byte, error) {
+	if wrongHTTPMethodUsed(r, w, method...) {
+		return nil, fmt.Errorf("Wrong HTTP method used")
+	}
+
+	data, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		logging.Debugf("Unable to read request body for URI: %s", r.RequestURI)
+		http.Error(w, "Unable to read request body", http.StatusInternalServerError)
+		return nil, err
+	}
+	return data, nil
+}
diff --git a/pkg/crc/api/client/client.go b/pkg/crc/api/client/client.go
new file mode 100644
index 000000000..ed34ef7de
--- /dev/null
+++ b/pkg/crc/api/client/client.go
@@ -0,0 +1,236 @@
+package client
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+
+	"github.com/code-ready/crc/pkg/crc/logging"
+)
+
+type Client struct {
+	client *http.Client
+	base   string
+}
+
+func New(client *http.Client, baseURL string) *Client {
+	return &Client{
+		client: client,
+		base:   baseURL,
+	}
+}
+
+func (c *Client) Version() (VersionResult, error) {
+	var vr = VersionResult{}
+	body, err := c.sendGetRequest("/version")
+	if err != nil {
+		logging.Error(err)
+		return vr, err
+	}
+	err = json.Unmarshal(body, &vr)
+	if err != nil {
+		logging.Error(err)
+		return vr, err
+	}
+	return vr, nil
+}
+
+func (c *Client) Status() (ClusterStatusResult, error) {
+	var sr = ClusterStatusResult{}
+	body, err := c.sendGetRequest("/status")
+	if err != nil {
+		logging.Error(err)
+		return sr, err
+	}
+	err = json.Unmarshal(body, &sr)
+	if err != nil {
+		logging.Error(err)
+		return sr, err
+	}
+	return sr, nil
+}
+
+func (c *Client) Start(config StartConfig) (StartResult, error) {
+	var sr = StartResult{}
+	var data = new(bytes.Buffer)
+
+	if config != (StartConfig{}) {
+		if err := json.NewEncoder(data).Encode(config); err != nil {
+			return sr, fmt.Errorf("Failed to encode data to JSON: %w", err)
+		}
+	}
+	body, err := c.sendPostRequest("/start", data)
+	if err != nil {
+		logging.Error(err)
+		return sr, err
+	}
+	err = json.Unmarshal(body, &sr)
+	if err != nil {
+		logging.Error(err)
+		return sr, err
+	}
+	return sr, nil
+}
+
+func (c *Client) Stop() (Result, error) {
+	var sr = Result{}
+	body, err := c.sendGetRequest("/stop")
+	if err != nil {
+		logging.Error(err)
+		return sr, err
+	}
+	err = json.Unmarshal(body, &sr)
+	if err != nil {
+		logging.Error(err)
+		return sr, err
+	}
+	return sr, nil
+}
+
+func (c *Client) Delete() (Result, error) {
+	var dr = Result{}
+	body, err := c.sendGetRequest("/delete")
+	if err != nil {
+		logging.Error(err)
+		return dr, err
+	}
+	err = json.Unmarshal(body, &dr)
+	if err != nil {
+		logging.Error(err)
+		return dr, err
+	}
+	return dr, nil
+}
+
+func (c *Client) WebconsoleURL() (ConsoleResult, error) {
+	var cr = ConsoleResult{}
+	body, err := c.sendGetRequest("/webconsoleurl")
+	if err != nil {
+		logging.Error(err)
+		return cr, err
+	}
+	err = json.Unmarshal(body, &cr)
+	if err != nil {
+		logging.Error(err)
+		return cr, err
+	}
+	return cr, nil
+}
+
+func (c *Client) GetConfig(configs []string) (GetConfigResult, error) {
+	var gcr = GetConfigResult{}
+	var data = new(bytes.Buffer)
+
+	if len(configs) > 0 {
+		cfg := getOrUnsetConfigRequest{
+			Properties: configs,
+		}
+		if err := json.NewEncoder(data).Encode(cfg); err != nil {
+			return gcr, fmt.Errorf("Failed to encode data to JSON: %w", err)
+		}
+	}
+	body, err := c.sendPostRequest("/config/get", data)
+	if err != nil {
+		logging.Error(err)
+		return gcr, err
+	}
+	err = json.Unmarshal(body, &gcr)
+	if err != nil {
+		logging.Error(err)
+		return gcr, err
+	}
+	return gcr, nil
+}
+
+func (c *Client) SetConfig(configs SetConfigRequest) (SetOrUnsetConfigResult, error) {
+	var scr = SetOrUnsetConfigResult{}
+	var data = new(bytes.Buffer)
+
+	if len(configs.Properties) == 0 {
+		return scr, fmt.Errorf("No config key value pair provided to set")
+	}
+
+	if err := json.NewEncoder(data).Encode(configs); err != nil {
+		return scr, fmt.Errorf("Failed to encode data to JSON: %w", err)
+	}
+
+	body, err := c.sendPostRequest("/config/set", data)
+	if err != nil {
+		logging.Error(err)
+		return scr, err
+	}
+
+	err = json.Unmarshal(body, &scr)
+	if err != nil {
+		return scr, err
+	}
+	return scr, nil
+}
+
+func (c *Client) UnsetConfig(configs []string) (SetOrUnsetConfigResult, error) {
+	var ucr = SetOrUnsetConfigResult{}
+	var data = new(bytes.Buffer)
+
+	cfg := getOrUnsetConfigRequest{
+		Properties: configs,
+	}
+	if err := json.NewEncoder(data).Encode(cfg); err != nil {
+		return ucr, fmt.Errorf("Failed to encode data to JSON: %w", err)
+	}
+	body, err := c.sendPostRequest("/config/unset", data)
+	if err != nil {
+		logging.Error(err)
+		return ucr, err
+	}
+	err = json.Unmarshal(body, &ucr)
+	if err != nil {
+		logging.Error(err)
+		return ucr, err
+	}
+	return ucr, nil
+}
+
+func (c *Client) sendGetRequest(url string) ([]byte, error) {
+	res, err := c.client.Get(fmt.Sprintf("%s%s", c.base, url))
+	if err != nil {
+		logging.Error(err)
+		return nil, err
+	}
+	if res.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("Error occurred sending GET request to : %s : %d", url, res.StatusCode)
+	}
+	defer res.Body.Close()
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return nil, fmt.Errorf("Unknown error reading response: %w", err)
+	}
+	return body, nil
+}
+
+func (c *Client) sendPostRequest(url string, data io.Reader) ([]byte, error) {
+	req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", c.base, url), data)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Content-Type", "application/json")
+
+	res, err := c.client.Do(req)
+	if err != nil {
+		logging.Error(err)
+		return nil, err
+	}
+	if res.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("Error occurred sending POST request to : %s : %d", url, res.StatusCode)
+	}
+
+	defer res.Body.Close()
+
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return nil, fmt.Errorf("Unknown error reading response: %w", err)
+	}
+	return body, nil
+}
diff --git a/pkg/crc/api/client/types.go b/pkg/crc/api/client/types.go
new file mode 100644
index 000000000..5767ae969
--- /dev/null
+++ b/pkg/crc/api/client/types.go
@@ -0,0 +1,68 @@
+package client
+
+import (
+	"github.com/code-ready/crc/pkg/crc/machine"
+)
+
+type VersionResult struct {
+	CrcVersion       string
+	CommitSha        string
+	OpenshiftVersion string
+	Success          bool
+}
+
+type Result struct {
+	Name    string
+	Success bool
+	Error   string
+}
+
+type StartResult struct {
+	Name           string
+	Status         string
+	Error          string
+	ClusterConfig  machine.ClusterConfig
+	KubeletStarted bool
+}
+
+type ClusterStatusResult struct {
+	Name             string
+	CrcStatus        string
+	OpenshiftStatus  string
+	OpenshiftVersion string
+	DiskUse          int64
+	DiskSize         int64
+	Error            string
+	Success          bool
+}
+
+type ConsoleResult struct {
+	ClusterConfig machine.ClusterConfig
+	Success       bool
+	Error         string
+}
+
+// setOrUnsetConfigResult struct is used to return the result of
+// setconfig/unsetconfig command
+type SetOrUnsetConfigResult struct {
+	Error      string
+	Properties []string
+}
+
+// getConfigResult struct is used to return the result of getconfig command
+type GetConfigResult struct {
+	Error   string
+	Configs map[string]interface{}
+}
+
+type StartConfig struct {
+	PullSecretPath string
+}
+
+type SetConfigRequest struct {
+	Properties map[string]interface{} `json:"properties"`
+}
+
+type getOrUnsetConfigRequest struct {
+	Properties []string `json:"properties"`
+}
diff --git a/pkg/crc/api/handlers.go b/pkg/crc/api/handlers.go
index 8684e905f..d862cb54b 100644
--- a/pkg/crc/api/handlers.go
+++ b/pkg/crc/api/handlers.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 
 	"github.com/code-ready/crc/cmd/crc/cmd/config"
+	"github.com/code-ready/crc/pkg/crc/api/client"
 	"github.com/code-ready/crc/pkg/crc/cluster"
 	crcConfig "github.com/code-ready/crc/pkg/crc/config"
 	"github.com/code-ready/crc/pkg/crc/errors"
@@ -17,10 +18,31 @@ import (
 )
 
 type Handler struct {
+	Logger        Logger
 	MachineClient AdaptedClient
 	Config        crcConfig.Storage
 }
 
+type Logger interface {
+	Messages() []string
+}
+
+func (h *Handler) Logs() string {
+	return encodeStructToJSON(&loggerResult{
+		Success:  true,
+		Messages: h.Logger.Messages(),
+	})
+}
+
+func NewHandler(config crcConfig.Storage, machine machine.Client) *Handler {
+	return &Handler{
+		MachineClient: &Adapter{
+			Underlying: machine,
+		},
+		Config: config,
+	}
+}
+
 func (h *Handler) Status() string {
 	clusterStatus := h.MachineClient.Status()
 	return encodeStructToJSON(clusterStatus)
@@ -37,7 +59,7 @@ func (h *Handler) Start(args json.RawMessage) string {
 	if args != nil {
 		parsedArgs, err = parseStartArgs(args)
 		if err != nil {
-			startErr := &StartResult{
+			startErr := &client.StartResult{
 				Name:  h.MachineClient.GetName(),
 				Error: fmt.Sprintf("Incorrect arguments given: %s", err.Error()),
 			}
@@ -45,7 +67,7 @@ func (h *Handler) Start(args json.RawMessage) string {
 		}
 	}
 	if err := preflight.StartPreflightChecks(h.Config); err != nil {
-		startErr := &StartResult{
+		startErr := &client.StartResult{
 			Name:  h.MachineClient.GetName(),
 			Error: err.Error(),
 		}
@@ -77,15 +99,8 @@ func getStartConfig(cfg crcConfig.Storage, args startArgs) machine.StartConfig {
 	}
 }
 
-type VersionResult struct {
-	CrcVersion       string
-	CommitSha        string
-	OpenshiftVersion string
-	Success          bool
-}
-
 func (h *Handler) GetVersion() string {
-	v := &VersionResult{
+	v := &client.VersionResult{
 		CrcVersion:       version.GetCRCVersion(),
 		CommitSha:        version.GetCommitSha(),
 		OpenshiftVersion: version.GetBundleVersion(),
@@ -105,7 +120,7 @@ func (h *Handler) GetWebconsoleInfo() string {
 }
 
 func (h *Handler) SetConfig(args json.RawMessage) string {
-	setConfigResult := setOrUnsetConfigResult{}
+	setConfigResult := client.SetOrUnsetConfigResult{}
 	if args == nil {
 		setConfigResult.Error = "No config keys provided"
 		return encodeStructToJSON(setConfigResult)
@@ -143,7 +158,7 @@ func (h *Handler) SetConfig(args json.RawMessage) string {
 }
 
 func (h *Handler) UnsetConfig(args json.RawMessage) string {
-	unsetConfigResult := setOrUnsetConfigResult{}
+	unsetConfigResult := client.SetOrUnsetConfigResult{}
 	if args == nil {
 		unsetConfigResult.Error = "No config keys provided"
 		return encodeStructToJSON(unsetConfigResult)
@@ -178,7 +193,7 @@ func (h *Handler) UnsetConfig(args json.RawMessage) string {
 }
 
 func (h *Handler) GetConfig(args json.RawMessage) string {
-	configResult := getConfigResult{}
+	configResult := client.GetConfigResult{}
 	if args == nil {
 		allConfigs := h.Config.AllConfigs()
 		configResult.Error = ""
@@ -222,7 +237,7 @@ func encodeStructToJSON(v interface{}) string {
 	s, err := json.Marshal(v)
 	if err != nil {
 		logging.Error(err.Error())
-		err := Result{
+		err := client.Result{
 			Success: false,
 			Error:   "Failed while encoding JSON to string",
 		}
@@ -233,7 +248,7 @@ func encodeStructToJSON(v interface{}) string {
 }
 
 func encodeErrorToJSON(errMsg string) string {
-	err := Result{
+	err := client.Result{
 		Success: false,
 		Error:   errMsg,
 	}
diff --git a/pkg/crc/api/types.go b/pkg/crc/api/types.go
index f68e13b83..c4aa6cac2 100644
--- a/pkg/crc/api/types.go
+++ b/pkg/crc/api/types.go
@@ -21,6 +21,7 @@ type RequestHandler interface {
 	UnsetConfig(json.RawMessage) string
 	GetConfig(json.RawMessage) string
 	GetWebconsoleInfo() string
+	Logs() string
 }
 
 // clusterOpsRequest struct is used to store the command request and associated socket
@@ -35,20 +36,12 @@ type commandRequest struct {
 	Args    json.RawMessage `json:"args,omitempty"`
 }
 
-// setOrUnsetConfigResult struct is used to return the result of
-// setconfig/unsetconfig command
-type setOrUnsetConfigResult struct {
-	Error      string
-	Properties []string
-}
-
-// getConfigResult struct is used to return the result of getconfig command
-type getConfigResult struct {
-	Error   string
-	Configs map[string]interface{}
-}
-
 // startArgs is used to get the pull secret file path as argument for start handler
 type startArgs struct {
 	PullSecretFile string `json:"pullSecretFile"`
 }
+
+type loggerResult struct {
+	Success  bool
+	Messages []string
+}
diff --git a/pkg/crc/cluster/kubeadminPassword.go b/pkg/crc/cluster/kubeadminPassword.go
new file mode 100644
index 000000000..7e2ae1052
--- /dev/null
+++ b/pkg/crc/cluster/kubeadminPassword.go
@@ -0,0 +1,94 @@
+package cluster
+
+import (
+	"crypto/rand"
+	"encoding/base64"
+	"fmt"
+	"math/big"
+
+	"github.com/code-ready/crc/pkg/crc/machine/bundle"
+	"github.com/code-ready/crc/pkg/crc/oc"
+	"golang.org/x/crypto/bcrypt"
+)
+
+// UpdateKubeAdminUserPassword does following
+// - Create and put updated kubeadmin password to ~/.crc/machine/crc/kubeadmin-password
+// - Update the htpasswd secret
+func UpdateKubeAdminUserPassword(kubeAdminPassword string, ocConfig oc.Config, bundle *bundle.CrcBundleInfo) error {
+	// In case of cluster started from stopped state.
+	if kubeAdminPassword == "" {
+		return nil
+	}
+	hashDeveloperPasswd, err := hashBcrypt("developer")
+	if err != nil {
+		return err
+	}
+
+	hashKubeAdminPasswd, err := hashBcrypt(kubeAdminPassword)
+	if err != nil {
+		return err
+	}
+	base64Data := getBase64(hashDeveloperPasswd, hashKubeAdminPasswd)
+
+	cmdArgs := []string{"patch", "secret", "htpass-secret", "-p",
+		fmt.Sprintf(`'{"data":{"htpasswd":"%s"}}'`, base64Data),
+		"-n", "openshift-config", "--type", "merge"}
+	_, stderr, err := ocConfig.RunOcCommandPrivate(cmdArgs...)
+	if err != nil {
+		return fmt.Errorf("Failed to update kubeadmin password %v: %s", err, stderr)
+	}
+	if err := bundle.UpdateKubeadminPassword(kubeAdminPassword); err != nil {
+		return err
+	}
+	return nil
+}
+
+// generateRandomPasswordHash generates a hash of a random ASCII password
+// 5char-5char-5char-5char
+// Copied from openshift/installer https://github.com/openshift/installer/blob/master/pkg/asset/password/password.go
+func GenerateRandomPasswordHash(length int) (string, error) {
+	const (
+		lowerLetters = "abcdefghijkmnopqrstuvwxyz"
+		upperLetters = "ABCDEFGHIJKLMNPQRSTUVWXYZ"
+		digits       = "23456789"
+		all          = lowerLetters + upperLetters + digits
+	)
+	var password string
+	for i := 0; i < length; i++ {
+		n, err := rand.Int(rand.Reader, big.NewInt(int64(len(all))))
+		if err != nil {
+			return "", err
+		}
+		newchar := string(all[n.Int64()])
+		if password == "" {
+			password = newchar
+		}
+		if i < length-1 {
+			n, err = rand.Int(rand.Reader, big.NewInt(int64(len(password)+1)))
+			if err != nil {
+				return "", err
+			}
+			j := n.Int64()
+			password = password[0:j] + newchar + password[j:]
+		}
+	}
+	pw := []rune(password)
+	for _, replace := range []int{5, 11, 17} {
+		pw[replace] = '-'
+	}
+
+	return string(pw), nil
+}
+
+func hashBcrypt(password string) (hash string, err error) {
+	passwordBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+	if err != nil {
+		return
+	}
+	return string(passwordBytes), nil
+}
+
+func getBase64(developerUserPassword, adminUserPassword string) string {
+	s := fmt.Sprintf("developer:%s\nkubeadmin:%s", developerUserPassword, adminUserPassword)
+	return base64.StdEncoding.EncodeToString([]byte(s))
+}
diff --git a/pkg/crc/constants/constants.go b/pkg/crc/constants/constants.go
index ff968e833..447f77687 100644
--- a/pkg/crc/constants/constants.go
+++ b/pkg/crc/constants/constants.go
@@ -27,10 +27,11 @@ const (
 	DaemonLogFile             = "crcd.log"
 	CrcLandingPageURL         = "https://cloud.redhat.com/openshift/create/local" // #nosec G101
 	DefaultPodmanURLBase      = "https://storage.googleapis.com/libpod-master-releases"
-	DefaultAdminHelperCliBase = "https://github.com/code-ready/admin-helper/releases/download/0.0.2"
+	DefaultAdminHelperCliBase = "https://github.com/code-ready/admin-helper/releases/download/0.0.3"
 	CRCMacTrayDownloadURL     = "https://github.com/code-ready/tray-macos/releases/download/v%s/crc-tray-macos.tar.gz"
 	CRCWindowsTrayDownloadURL = "https://github.com/code-ready/tray-windows/releases/download/v%s/crc-tray-windows.zip"
 	DefaultContext            = "admin"
+	DaemonHTTPEndpoint        = "http://unix/api"
 
 	VSockGateway = "192.168.127.1"
 	VsockSSHPort = 2222
diff --git a/pkg/crc/logging/inmemory.go b/pkg/crc/logging/inmemory.go
new file mode 100644
index 000000000..756014739
--- /dev/null
+++ b/pkg/crc/logging/inmemory.go
@@ -0,0 +1,38 @@
+package logging
+
+import (
+	"container/ring"
+
+	"github.com/sirupsen/logrus"
+)
+
+// This hook keeps in memory n messages from error to info level
+type inMemoryHook struct {
+	messages *ring.Ring
+}
+
+func newInMemoryHook(size int) *inMemoryHook {
+	return &inMemoryHook{
+		messages: ring.New(size),
+	}
+}
+
+func (h *inMemoryHook) Levels() []logrus.Level {
+	return []logrus.Level{logrus.InfoLevel, logrus.WarnLevel, logrus.ErrorLevel}
+}
+
+func (h *inMemoryHook) Fire(entry *logrus.Entry) error {
+	h.messages.Value = entry.Message
+	h.messages = h.messages.Next()
+	return nil
+}
+
+func (h *inMemoryHook) Messages() []string {
+	var ret []string
+	h.messages.Do(func(elem interface{}) {
+		if str, ok := elem.(string); ok {
+			ret = append(ret, str)
+		}
+	})
+	return ret
+}
diff --git a/pkg/crc/logging/logging.go b/pkg/crc/logging/logging.go
index 370730795..f5d7bb797 100644
--- a/pkg/crc/logging/logging.go
+++ b/pkg/crc/logging/logging.go
@@ -14,6 +14,7 @@ var (
 	logfile       *os.File
 	LogLevel      string
 	originalHooks = logrus.LevelHooks{}
+	Memory        = newInMemoryHook(100)
 )
 
 func OpenLogFile(path string) (*os.File, error) {
@@ -33,8 +34,11 @@ func CloseLogging() {
 	logrus.StandardLogger().ReplaceHooks(make(logrus.LevelHooks))
 }
 
-func BackupLogFile() {
-	os.Rename(logfile.Name(), fmt.Sprintf("%s_%s", logfile.Name(), time.Now().Format("20060102150405"))) // nolint
+func BackupLogFile() error {
+	if logfile == nil {
+		return nil
+	}
+	return os.Rename(logfile.Name(), fmt.Sprintf("%s_%s", logfile.Name(), time.Now().Format("20060102150405")))
 }
 
 func InitLogrus(logLevel, logFilePath string) {
@@ -53,6 +57,8 @@ func InitLogrus(logLevel, logFilePath string) {
 		level = logrus.InfoLevel
 	}
 
+	logrus.AddHook(Memory)
+
 	// Add hook to send error/fatal to stderr
 	logrus.AddHook(newstdErrHook(level, &logrus.TextFormatter{
 		ForceColors:            terminal.IsTerminal(int(os.Stderr.Fd())),
diff --git a/pkg/crc/machine/bundle/metadata.go b/pkg/crc/machine/bundle/metadata.go
index d55efab0c..e4f874178 100644
--- a/pkg/crc/machine/bundle/metadata.go
+++ b/pkg/crc/machine/bundle/metadata.go
@@ -111,13 +111,27 @@ func (bundle *CrcBundleInfo) GetInitramfsPath() string {
 }
 
 func (bundle *CrcBundleInfo) GetKubeadminPassword() (string, error) {
-	rawData, err := ioutil.ReadFile(bundle.resolvePath(bundle.ClusterInfo.KubeadminPasswordFile))
+	// First check if ~/.crc/machine/crc/kubeadmin-password file exist and read password from there
+	kubeAdminPasswordFile := filepath.Join(constants.MachineInstanceDir, constants.DefaultName, bundle.ClusterInfo.KubeadminPasswordFile)
+	if _, err := os.Stat(kubeAdminPasswordFile); err != nil {
+		kubeAdminPasswordFile = bundle.resolvePath(bundle.ClusterInfo.KubeadminPasswordFile)
+	}
+	rawData, err := ioutil.ReadFile(kubeAdminPasswordFile)
 	if err != nil {
 		return "", err
 	}
 	return strings.TrimSpace(string(rawData)), nil
 }
 
+func (bundle *CrcBundleInfo) UpdateKubeadminPassword(kubeadminPassword string) error {
+	kubeAdminPasswordFile := filepath.Join(constants.MachineInstanceDir, constants.DefaultName, bundle.ClusterInfo.KubeadminPasswordFile)
+	err := ioutil.WriteFile(kubeAdminPasswordFile, []byte(kubeadminPassword), 0600)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 func (bundle *CrcBundleInfo) GetBundleBuildTime() (time.Time, error) {
 	return time.Parse(time.RFC3339, strings.TrimSpace(bundle.BuildInfo.BuildTime))
 }
diff --git a/pkg/crc/machine/bundle/repository.go b/pkg/crc/machine/bundle/repository.go
index 135195128..00e680fce 100644
--- a/pkg/crc/machine/bundle/repository.go
+++ b/pkg/crc/machine/bundle/repository.go
@@ -8,9 +8,11 @@ import (
 	"path/filepath"
 	"runtime"
 	"strings"
+	"time"
 
 	"github.com/Masterminds/semver"
 	"github.com/code-ready/crc/pkg/crc/constants"
+	crcerrors "github.com/code-ready/crc/pkg/crc/errors"
 	"github.com/code-ready/crc/pkg/extract"
 	crcos "github.com/code-ready/crc/pkg/os"
 	"github.com/pkg/errors"
@@ -120,7 +122,12 @@ func (repo *Repository) Extract(path string) error {
 	}
 
 	bundleDir := strings.TrimSuffix(bundleName, bundleExtension)
-	return os.Rename(filepath.Join(tmpDir, bundleDir), filepath.Join(repo.CacheDir, bundleDir))
+	return crcerrors.RetryAfter(time.Minute, func() error {
+		if err := os.Rename(filepath.Join(tmpDir, bundleDir), filepath.Join(repo.CacheDir, bundleDir)); err != nil {
+			return crcerrors.RetriableError{Err: err}
+		}
+		return nil
+	}, 5*time.Second)
 }
 
 var defaultRepo = &Repository{
diff --git a/pkg/crc/machine/console.go b/pkg/crc/machine/console.go
index 630496204..c5c1a5ff0 100644
--- a/pkg/crc/machine/console.go
+++ b/pkg/crc/machine/console.go
@@ -24,7 +24,7 @@ func (client *client) GetConsoleURL() (*ConsoleResult, error) {
 		return nil, errors.Wrap(err, "Error loading bundle metadata")
 	}
 
-	clusterConfig, err := getClusterConfig(crcBundleMetadata)
+	clusterConfig, err := getClusterConfig(crcBundleMetadata, "")
 	if err != nil {
 		return nil, errors.Wrap(err, "Error loading cluster configuration")
 	}
diff --git a/pkg/crc/machine/delete.go b/pkg/crc/machine/delete.go
index ca24ad5f1..535ea55ec 100644
--- a/pkg/crc/machine/delete.go
+++ b/pkg/crc/machine/delete.go
@@ -1,13 +1,11 @@
 package machine
 
 import (
-	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/pkg/errors"
 )
 
 func (client *client) Delete() error {
 	libMachineAPIClient, cleanup := createLibMachineClient()
-	defer logging.BackupLogFile()
 	defer cleanup()
 	host, err := libMachineAPIClient.Load(client.name)
 
diff --git a/pkg/crc/machine/hyperkit/constants.go b/pkg/crc/machine/hyperkit/constants.go
index a3cfe45f8..c11fcca8a 100644
--- a/pkg/crc/machine/hyperkit/constants.go
+++ b/pkg/crc/machine/hyperkit/constants.go
@@ -6,7 +6,7 @@ import "fmt"
 
 const (
 	MachineDriverCommand = "crc-driver-hyperkit"
-	MachineDriverVersion = "0.12.11"
+	MachineDriverVersion = "0.12.12"
 	HyperKitCommand      = "hyperkit"
 	HyperKitVersion      = "v0.20200224-44-gb54460"
 )
diff --git a/pkg/crc/machine/machine.go b/pkg/crc/machine/machine.go
index ea0e12050..ae7fdd3df 100644
--- a/pkg/crc/machine/machine.go
+++ b/pkg/crc/machine/machine.go
@@ -12,11 +12,14 @@ import (
 	"github.com/code-ready/machine/libmachine/drivers"
 )
 
-func getClusterConfig(bundleInfo *bundle.CrcBundleInfo) (*ClusterConfig, error) {
+func getClusterConfig(bundleInfo *bundle.CrcBundleInfo, generatedKubeadminPassword string) (*ClusterConfig, error) {
 	kubeadminPassword, err := bundleInfo.GetKubeadminPassword()
 	if err != nil {
 		return nil, fmt.Errorf("Error reading kubeadmin password from bundle %v", err)
 	}
+	if generatedKubeadminPassword != "" {
+		kubeadminPassword = generatedKubeadminPassword
+	}
 	proxyConfig, err := getProxyConfig(bundleInfo.ClusterInfo.BaseDomain)
 	if err != nil {
 		return nil, err
diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go
index 61fd5f031..84e54b0bc 100644
--- a/pkg/crc/machine/start.go
+++ b/pkg/crc/machine/start.go
@@ -108,6 +108,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 
 	// Pre-VM start
 	var host *host.Host
+	var kubeAdminUserPassword string
 	exists, err := client.Exists()
 	if err != nil {
 		return nil, errors.Wrap(err, "Cannot determine if VM exists")
@@ -131,6 +132,11 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 			NetworkMode: client.networkMode(),
 		}
 
+		kubeAdminUserPassword, err = cluster.GenerateRandomPasswordHash(23)
+		if err != nil {
+			return nil, errors.Wrap(err, "Cannot generate the kubeadmin user password")
+		}
+
 		crcBundleMetadata, err = getCrcBundleInfo(startConfig.BundlePath)
 		if err != nil {
 			return nil, errors.Wrap(err, "Error getting bundle metadata")
@@ -180,7 +186,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 		}
 		if vmState == state.Running {
 			logging.Infof("A CodeReady Containers VM for OpenShift %s is already running", crcBundleMetadata.GetOpenshiftVersion())
-			clusterConfig, err := getClusterConfig(crcBundleMetadata)
+			clusterConfig, err := getClusterConfig(crcBundleMetadata, kubeAdminUserPassword)
 			if err != nil {
 				return nil, errors.Wrap(err, "Cannot create cluster configuration")
 			}
@@ -222,7 +228,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 		}
 	}
 
-	clusterConfig, err := getClusterConfig(crcBundleMetadata)
+	clusterConfig, err := getClusterConfig(crcBundleMetadata, kubeAdminUserPassword)
 	if err != nil {
 		return nil, errors.Wrap(err, "Cannot create cluster configuration")
 	}
@@ -366,6 +372,10 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 		return nil, errors.Wrap(err, "Failed to update cluster pull secret")
 	}
 
+	if err := cluster.UpdateKubeAdminUserPassword(kubeAdminUserPassword, ocConfig, crcBundleMetadata); err != nil {
+		return nil, errors.Wrap(err, "Failed to update kubeadmin user password")
+	}
+
 	if err := cluster.EnsureClusterIDIsNotEmpty(ocConfig); err != nil {
 		return nil, errors.Wrap(err, "Failed to update cluster ID")
 	}
diff --git a/pkg/crc/preflight/preflight_darwin.go b/pkg/crc/preflight/preflight_darwin.go
index a61486ea7..47ab3b07e 100644
--- a/pkg/crc/preflight/preflight_darwin.go
+++ b/pkg/crc/preflight/preflight_darwin.go
@@ -113,7 +113,7 @@ func getAllPreflightChecks() []Check {
 	return getPreflightChecks(true, true, network.DefaultMode)
 }
 
-func getPreflightChecks(_ bool, trayAutostart bool, mode network.Mode) []Check {
+func getPreflightChecks(experimentalFeatures bool, trayAutostart bool, mode network.Mode) []Check {
 	checks := []Check{}
 
 	checks = append(checks, nonWinPreflightChecks[:]...)
@@ -121,18 +121,20 @@ func getPreflightChecks(_ bool, trayAutostart bool, mode network.Mode) []Check {
 	checks = append(checks, hyperkitPreflightChecks(mode)...)
 	checks = append(checks, dnsPreflightChecks[:]...)
 
-	if trayAutostart || mode == network.VSockMode {
+	switch mode {
+	case network.DefaultMode:
+		checks = append(checks, resolverPreflightChecks[:]...)
+	case network.VSockMode:
 		checks = append(checks, daemonSetupChecks[:]...)
 	}
 
-	if trayAutostart {
+	if experimentalFeatures && trayAutostart {
+		if mode != network.VSockMode {
+			checks = append(checks, daemonSetupChecks[:]...)
+		}
 		checks = append(checks, traySetupChecks[:]...)
 	}
 
-	if mode == network.DefaultMode {
-		checks = append(checks, resolverPreflightChecks[:]...)
-	}
-
 	checks = append(checks, bundleCheck)
 	return checks
 }
diff --git a/pkg/crc/version/version.go b/pkg/crc/version/version.go
index 445a9f0ca..f74991867 100644
--- a/pkg/crc/version/version.go
+++ b/pkg/crc/version/version.go
@@ -29,7 +29,7 @@ var (
 const (
 	releaseInfoLink = "https://mirror.openshift.com/pub/openshift-v4/clients/crc/latest/release-info.json"
 	// Tray version to be embedded in executable
-	crcMacTrayVersion = "1.0.3"
+	crcMacTrayVersion = "1.0.4"
 	// Windows forms application version type major.minor.buildnumber.revesion
 	crcWindowsTrayVersion = "0.4.0.0"
 )
diff --git a/vendor/github.com/code-ready/gvisor-tap-vsock/pkg/services/dns/dns.go b/vendor/github.com/code-ready/gvisor-tap-vsock/pkg/services/dns/dns.go
index ed6a40763..1ffd1cacf 100644
--- a/vendor/github.com/code-ready/gvisor-tap-vsock/pkg/services/dns/dns.go
+++ b/vendor/github.com/code-ready/gvisor-tap-vsock/pkg/services/dns/dns.go
@@ -27,8 +27,6 @@ func (h *dnsHandler) handle(w dns.ResponseWriter, r *dns.Msg) {
 
 func (h *dnsHandler) addAnswers(m *dns.Msg) {
 	for _, q := range m.Question {
-		log.Debugf("DNS query for %s", q.String())
-
 		for _, zone := range h.zones {
 			zoneSuffix := fmt.Sprintf(".%s", zone.Name)
 			if strings.HasSuffix(q.Name, zoneSuffix) {
diff --git a/vendor/github.com/code-ready/gvisor-tap-vsock/pkg/tap/ip_pool.go b/vendor/github.com/code-ready/gvisor-tap-vsock/pkg/tap/ip_pool.go
index ce158dbe4..9df2d17c6 100644
--- a/vendor/github.com/code-ready/gvisor-tap-vsock/pkg/tap/ip_pool.go
+++ b/vendor/github.com/code-ready/gvisor-tap-vsock/pkg/tap/ip_pool.go
@@ -24,7 +24,13 @@ func NewIPPool(base *net.IPNet) *IPPool {
 }
 
 func (p *IPPool) Leases() map[string]int {
-	return p.leases
+	p.lock.Lock()
+	defer p.lock.Unlock()
+	leases := map[string]int{}
+	for key, value := range p.leases {
+		leases[key] = value
+	}
+	return leases
 }
 
 func (p *IPPool) Mask() int {
diff --git a/vendor/golang.org/x/crypto/bcrypt/base64.go b/vendor/golang.org/x/crypto/bcrypt/base64.go
new file mode 100644
index 000000000..fc3116090
--- /dev/null
+++ b/vendor/golang.org/x/crypto/bcrypt/base64.go
@@ -0,0 +1,35 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bcrypt
+
+import "encoding/base64"
+
+const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+
+var bcEncoding = base64.NewEncoding(alphabet)
+
+func base64Encode(src []byte) []byte {
+	n := bcEncoding.EncodedLen(len(src))
+	dst := make([]byte, n)
+	bcEncoding.Encode(dst, src)
+	for dst[n-1] == '=' {
+		n--
+	}
+	return dst[:n]
+}
+
+func base64Decode(src []byte) ([]byte, error) {
+	numOfEquals := 4 - (len(src) % 4)
+	for i := 0; i < numOfEquals; i++ {
+		src = append(src, '=')
+	}
+
+	dst := make([]byte, bcEncoding.DecodedLen(len(src)))
+	n, err := bcEncoding.Decode(dst, src)
+	if err != nil {
+		return nil, err
+	}
+	return dst[:n], nil
+}
diff --git a/vendor/golang.org/x/crypto/bcrypt/bcrypt.go b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go
new file mode 100644
index 000000000..aeb73f81a
--- /dev/null
+++ b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go
@@ -0,0 +1,295 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
+// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
+package bcrypt // import "golang.org/x/crypto/bcrypt"
+
+// The code is a port of Provos and Mazières's C implementation.
+import (
+	"crypto/rand"
+	"crypto/subtle"
+	"errors"
+	"fmt"
+	"io"
+	"strconv"
+
+	"golang.org/x/crypto/blowfish"
+)
+
+const (
+	MinCost     int = 4  // the minimum allowable cost as passed in to GenerateFromPassword
+	MaxCost     int = 31 // the maximum allowable cost as passed in to GenerateFromPassword
+	DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
+)
+
+// The error returned from CompareHashAndPassword when a password and hash do
+// not match.
+var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
+
+// The error returned from CompareHashAndPassword when a hash is too short to
+// be a bcrypt hash.
+var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
+
+// The error returned from CompareHashAndPassword when a hash was created with
+// a bcrypt algorithm newer than this implementation.
+type HashVersionTooNewError byte
+
+func (hv HashVersionTooNewError) Error() string {
+	return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
+}
+
+// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
+type InvalidHashPrefixError byte
+
+func (ih InvalidHashPrefixError) Error() string {
+	return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
+}
+
+type InvalidCostError int
+
+func (ic InvalidCostError) Error() string {
+	return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost))
+}
+
+const (
+	majorVersion       = '2'
+	minorVersion       = 'a'
+	maxSaltSize        = 16
+	maxCryptedHashSize = 23
+	encodedSaltSize    = 22
+	encodedHashSize    = 31
+	minHashSize        = 59
+)
+
+// magicCipherData is an IV for the 64 Blowfish encryption calls in
+// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
+var magicCipherData = []byte{
+	0x4f, 0x72, 0x70, 0x68,
+	0x65, 0x61, 0x6e, 0x42,
+	0x65, 0x68, 0x6f, 0x6c,
+	0x64, 0x65, 0x72, 0x53,
+	0x63, 0x72, 0x79, 0x44,
+	0x6f, 0x75, 0x62, 0x74,
+}
+
+type hashed struct {
+	hash  []byte
+	salt  []byte
+	cost  int // allowed range is MinCost to MaxCost
+	major byte
+	minor byte
+}
+
+// GenerateFromPassword returns the bcrypt hash of the password at the given
+// cost. If the cost given is less than MinCost, the cost will be set to
+// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
+// to compare the returned hashed password with its cleartext version.
+func GenerateFromPassword(password []byte, cost int) ([]byte, error) {
+	p, err := newFromPassword(password, cost)
+	if err != nil {
+		return nil, err
+	}
+	return p.Hash(), nil
+}
+
+// CompareHashAndPassword compares a bcrypt hashed password with its possible
+// plaintext equivalent. Returns nil on success, or an error on failure.
+func CompareHashAndPassword(hashedPassword, password []byte) error {
+	p, err := newFromHash(hashedPassword)
+	if err != nil {
+		return err
+	}
+
+	otherHash, err := bcrypt(password, p.cost, p.salt)
+	if err != nil {
+		return err
+	}
+
+	otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
+	if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
+		return nil
+	}
+
+	return ErrMismatchedHashAndPassword
+}
+
+// Cost returns the hashing cost used to create the given hashed
+// password. When, in the future, the hashing cost of a password system needs
+// to be increased in order to adjust for greater computational power, this
+// function allows one to establish which passwords need to be updated.
+func Cost(hashedPassword []byte) (int, error) {
+	p, err := newFromHash(hashedPassword)
+	if err != nil {
+		return 0, err
+	}
+	return p.cost, nil
+}
+
+func newFromPassword(password []byte, cost int) (*hashed, error) {
+	if cost < MinCost {
+		cost = DefaultCost
+	}
+	p := new(hashed)
+	p.major = majorVersion
+	p.minor = minorVersion
+
+	err := checkCost(cost)
+	if err != nil {
+		return nil, err
+	}
+	p.cost = cost
+
+	unencodedSalt := make([]byte, maxSaltSize)
+	_, err = io.ReadFull(rand.Reader, unencodedSalt)
+	if err != nil {
+		return nil, err
+	}
+
+	p.salt = base64Encode(unencodedSalt)
+	hash, err := bcrypt(password, p.cost, p.salt)
+	if err != nil {
+		return nil, err
+	}
+	p.hash = hash
+	return p, err
+}
+
+func newFromHash(hashedSecret []byte) (*hashed, error) {
+	if len(hashedSecret) < minHashSize {
+		return nil, ErrHashTooShort
+	}
+	p := new(hashed)
+	n, err := p.decodeVersion(hashedSecret)
+	if err != nil {
+		return nil, err
+	}
+	hashedSecret = hashedSecret[n:]
+	n, err = p.decodeCost(hashedSecret)
+	if err != nil {
+		return nil, err
+	}
+	hashedSecret = hashedSecret[n:]
+
+	// The "+2" is here because we'll have to append at most 2 '=' to the salt
+	// when base64 decoding it in expensiveBlowfishSetup().
+	p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
+	copy(p.salt, hashedSecret[:encodedSaltSize])
+
+	hashedSecret = hashedSecret[encodedSaltSize:]
+	p.hash = make([]byte, len(hashedSecret))
+	copy(p.hash, hashedSecret)
+
+	return p, nil
+}
+
+func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
+	cipherData := make([]byte, len(magicCipherData))
+	copy(cipherData, magicCipherData)
+
+	c, err := expensiveBlowfishSetup(password, uint32(cost), salt)
+	if err != nil {
+		return nil, err
+	}
+
+	for i := 0; i < 24; i += 8 {
+		for j := 0; j < 64; j++ {
+			c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
+		}
+	}
+
+	// Bug compatibility with C bcrypt implementations. We only encode 23 of
+	// the 24 bytes encrypted.
+	hsh := base64Encode(cipherData[:maxCryptedHashSize])
+	return hsh, nil
+}
+
+func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
+	csalt, err := base64Decode(salt)
+	if err != nil {
+		return nil, err
+	}
+
+	// Bug compatibility with C bcrypt implementations. They use the trailing
+	// NULL in the key string during expansion.
+	// We copy the key to prevent changing the underlying array.
+	ckey := append(key[:len(key):len(key)], 0)
+
+	c, err := blowfish.NewSaltedCipher(ckey, csalt)
+	if err != nil {
+		return nil, err
+	}
+
+	var i, rounds uint64
+	rounds = 1 << cost
+	for i = 0; i < rounds; i++ {
+		blowfish.ExpandKey(ckey, c)
+		blowfish.ExpandKey(csalt, c)
+	}
+
+	return c, nil
+}
+
+func (p *hashed) Hash() []byte {
+	arr := make([]byte, 60)
+	arr[0] = '$'
+	arr[1] = p.major
+	n := 2
+	if p.minor != 0 {
+		arr[2] = p.minor
+		n = 3
+	}
+	arr[n] = '$'
+	n++
+	copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
+	n += 2
+	arr[n] = '$'
+	n++
+	copy(arr[n:], p.salt)
+	n += encodedSaltSize
+	copy(arr[n:], p.hash)
+	n += encodedHashSize
+	return arr[:n]
+}
+
+func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
+	if sbytes[0] != '$' {
+		return -1, InvalidHashPrefixError(sbytes[0])
+	}
+	if sbytes[1] > majorVersion {
+		return -1, HashVersionTooNewError(sbytes[1])
+	}
+	p.major = sbytes[1]
+	n := 3
+	if sbytes[2] != '$' {
+		p.minor = sbytes[2]
+		n++
+	}
+	return n, nil
+}
+
+// sbytes should begin where decodeVersion left off.
+func (p *hashed) decodeCost(sbytes []byte) (int, error) {
+	cost, err := strconv.Atoi(string(sbytes[0:2]))
+	if err != nil {
+		return -1, err
+	}
+	err = checkCost(cost)
+	if err != nil {
+		return -1, err
+	}
+	p.cost = cost
+	return 3, nil
+}
+
+func (p *hashed) String() string {
+	return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
+}
+
+func checkCost(cost int) error {
+	if cost < MinCost || cost > MaxCost {
+		return InvalidCostError(cost)
+	}
+	return nil
+}
diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go b/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go
index c474e5a80..94c71ac1a 100644
--- a/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go
+++ b/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.11 && gc && !purego
 // +build go1.11,gc,!purego
 
 package chacha20
diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go b/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go
index 3e8a609fb..025b49897 100644
--- a/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go
+++ b/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build (!arm64 && !s390x && !ppc64le) || (arm64 && !go1.11) || !gc || purego
 // +build !arm64,!s390x,!ppc64le arm64,!go1.11 !gc purego
 
 package chacha20
diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.go b/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.go
index 2806c6325..da420b2e9 100644
--- a/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.go
+++ b/vendor/golang.org/x/crypto/chacha20/chacha_ppc64le.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build gc && !purego
 // +build gc,!purego
 
 package chacha20
diff --git a/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go b/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go
index a0774dde1..c5898db46 100644
--- a/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go
+++ b/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build gc && !purego
 // +build gc,!purego
 
 package chacha20
diff --git a/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.go b/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.go
index 877b6de29..84858480d 100644
--- a/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.go
+++ b/vendor/golang.org/x/crypto/curve25519/curve25519_amd64.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build amd64 && gc && !purego
 // +build amd64,gc,!purego
 
 package curve25519
diff --git a/vendor/golang.org/x/crypto/curve25519/curve25519_noasm.go b/vendor/golang.org/x/crypto/curve25519/curve25519_noasm.go
index 80d3300af..259728af7 100644
--- a/vendor/golang.org/x/crypto/curve25519/curve25519_noasm.go
+++ b/vendor/golang.org/x/crypto/curve25519/curve25519_noasm.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !amd64 || !gc || purego
 // +build !amd64 !gc purego
 
 package curve25519
diff --git a/vendor/golang.org/x/crypto/ed25519/ed25519.go b/vendor/golang.org/x/crypto/ed25519/ed25519.go
index c7f8c7e64..71ad917da 100644
--- a/vendor/golang.org/x/crypto/ed25519/ed25519.go
+++ b/vendor/golang.org/x/crypto/ed25519/ed25519.go
@@ -5,6 +5,7 @@
 // In Go 1.13, the ed25519 package was promoted to the standard library as
 // crypto/ed25519, and this package became a wrapper for the standard library one.
 //
+//go:build !go1.13
 // +build !go1.13
 
 // Package ed25519 implements the Ed25519 signature algorithm. See
diff --git a/vendor/golang.org/x/crypto/ed25519/ed25519_go113.go b/vendor/golang.org/x/crypto/ed25519/ed25519_go113.go
index d1448d8d2..b5974dc8b 100644
--- a/vendor/golang.org/x/crypto/ed25519/ed25519_go113.go
+++ b/vendor/golang.org/x/crypto/ed25519/ed25519_go113.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 // Package ed25519 implements the Ed25519 signature algorithm. See
diff --git a/vendor/golang.org/x/crypto/internal/subtle/aliasing.go b/vendor/golang.org/x/crypto/internal/subtle/aliasing.go
index 281c27ef0..4fad24f8d 100644
--- a/vendor/golang.org/x/crypto/internal/subtle/aliasing.go
+++ b/vendor/golang.org/x/crypto/internal/subtle/aliasing.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !purego
 // +build !purego
 
 // Package subtle implements functions that are often useful in cryptographic
diff --git a/vendor/golang.org/x/crypto/internal/subtle/aliasing_purego.go b/vendor/golang.org/x/crypto/internal/subtle/aliasing_purego.go
index e20a29659..80ccbed2c 100644
--- a/vendor/golang.org/x/crypto/internal/subtle/aliasing_purego.go
+++ b/vendor/golang.org/x/crypto/internal/subtle/aliasing_purego.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build purego
 // +build purego
 
 // Package subtle implements functions that are often useful in cryptographic
diff --git a/vendor/golang.org/x/crypto/poly1305/bits_compat.go b/vendor/golang.org/x/crypto/poly1305/bits_compat.go
index 157a69f61..45b5c966b 100644
--- a/vendor/golang.org/x/crypto/poly1305/bits_compat.go
+++ b/vendor/golang.org/x/crypto/poly1305/bits_compat.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !go1.13
 // +build !go1.13
 
 package poly1305
diff --git a/vendor/golang.org/x/crypto/poly1305/bits_go1.13.go b/vendor/golang.org/x/crypto/poly1305/bits_go1.13.go
index a0a185f0f..ed52b3418 100644
--- a/vendor/golang.org/x/crypto/poly1305/bits_go1.13.go
+++ b/vendor/golang.org/x/crypto/poly1305/bits_go1.13.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package poly1305
diff --git a/vendor/golang.org/x/crypto/poly1305/mac_noasm.go b/vendor/golang.org/x/crypto/poly1305/mac_noasm.go
index af6c94f92..f184b67d9 100644
--- a/vendor/golang.org/x/crypto/poly1305/mac_noasm.go
+++ b/vendor/golang.org/x/crypto/poly1305/mac_noasm.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build (!amd64 && !ppc64le && !s390x) || !gc || purego
 // +build !amd64,!ppc64le,!s390x !gc purego
 
 package poly1305
diff --git a/vendor/golang.org/x/crypto/poly1305/sum_amd64.go b/vendor/golang.org/x/crypto/poly1305/sum_amd64.go
index cf3a69ed3..6d522333f 100644
--- a/vendor/golang.org/x/crypto/poly1305/sum_amd64.go
+++ b/vendor/golang.org/x/crypto/poly1305/sum_amd64.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build gc && !purego
 // +build gc,!purego
 
 package poly1305
diff --git a/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.go b/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.go
index cb4b7185d..4a069941a 100644
--- a/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.go
+++ b/vendor/golang.org/x/crypto/poly1305/sum_ppc64le.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build gc && !purego
 // +build gc,!purego
 
 package poly1305
diff --git a/vendor/golang.org/x/crypto/poly1305/sum_s390x.go b/vendor/golang.org/x/crypto/poly1305/sum_s390x.go
index 188a665e1..62cc9f847 100644
--- a/vendor/golang.org/x/crypto/poly1305/sum_s390x.go
+++ b/vendor/golang.org/x/crypto/poly1305/sum_s390x.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build gc && !purego
 // +build gc,!purego
 
 package poly1305
diff --git a/vendor/golang.org/x/crypto/ssh/server.go b/vendor/golang.org/x/crypto/ssh/server.go
index 7d42a8c88..b6911e830 100644
--- a/vendor/golang.org/x/crypto/ssh/server.go
+++ b/vendor/golang.org/x/crypto/ssh/server.go
@@ -572,6 +572,10 @@ userAuthLoop:
 				perms = candidate.perms
 			}
 		case "gssapi-with-mic":
+			if config.GSSAPIWithMICConfig == nil {
+				authErr = errors.New("ssh: gssapi-with-mic auth not configured")
+				break
+			}
 			gssapiConfig := config.GSSAPIWithMICConfig
 			userAuthRequestGSSAPI, err := parseGSSAPIPayload(userAuthReq.Payload)
 			if err != nil {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index aefacc767..ef8f245b7 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -36,7 +36,7 @@ github.com/cheggaaa/pb/v3/termutil
 ## explicit
 github.com/code-ready/clicumber/testsuite
 github.com/code-ready/clicumber/util
-# github.com/code-ready/gvisor-tap-vsock v0.0.0-20210216090540-914d522b9c38
+# github.com/code-ready/gvisor-tap-vsock v0.0.0-20210308122700-d61f9aac135c
 ## explicit
 github.com/code-ready/gvisor-tap-vsock/pkg/client
 github.com/code-ready/gvisor-tap-vsock/pkg/services/dns
@@ -290,8 +290,9 @@ github.com/xtgo/uuid
 ## explicit
 github.com/zalando/go-keyring
 github.com/zalando/go-keyring/secret_service
-# golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c
+# golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
 ## explicit
+golang.org/x/crypto/bcrypt
 golang.org/x/crypto/blowfish
 golang.org/x/crypto/chacha20
 golang.org/x/crypto/curve25519


Test patch:
diff --git a/pkg/crc/api/api_http_test.go b/pkg/crc/api/api_http_test.go
new file mode 100644
index 000000000..9be5e88f4
--- /dev/null
+++ b/pkg/crc/api/api_http_test.go
@@ -0,0 +1,125 @@
+package api
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	apiClient "github.com/code-ready/crc/pkg/crc/api/client"
+	"github.com/code-ready/crc/pkg/crc/machine"
+	"github.com/code-ready/crc/pkg/crc/machine/fakemachine"
+	"github.com/code-ready/crc/pkg/crc/version"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestHTTPApi(t *testing.T) {
+	fakeMachine := fakemachine.NewClient()
+	config := setupNewInMemoryConfig()
+
+	ts := httptest.NewServer(NewMux(config, fakeMachine))
+	defer ts.Close()
+
+	client := apiClient.New(http.DefaultClient, ts.URL)
+	vr, err := client.Version()
+	assert.NoError(t, err)
+	assert.Equal(
+		t,
+		apiClient.VersionResult{
+			CrcVersion:       version.GetCRCVersion(),
+			OpenshiftVersion: version.GetBundleVersion(),
+			CommitSha:        version.GetCommitSha(),
+			Success:          true,
+		},
+		vr,
+	)
+
+	statusResult, err := client.Status()
+	assert.NoError(t, err)
+	assert.Equal(
+		t,
+		apiClient.ClusterStatusResult{
+			Name:             "crc",
+			CrcStatus:        "Running",
+			OpenshiftStatus:  "Running",
+			OpenshiftVersion: "4.5.1",
+			DiskUse:          int64(10000000000),
+			DiskSize:         int64(20000000000),
+			Success:          true,
+		},
+		statusResult,
+	)
+
+	var startConfig = apiClient.StartConfig{}
+	startResult, err := client.Start(startConfig)
+	assert.NoError(t, err)
+	assert.Equal(
+		t,
+		apiClient.StartResult{
+			Name:           "crc",
+			Status:         "",
+			Error:          "",
+			KubeletStarted: true,
+			ClusterConfig: machine.ClusterConfig{
+				ClusterCACert: "MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ",
+				KubeConfig:    "/tmp/kubeconfig",
+				KubeAdminPass: "foobar",
+				ClusterAPI:    "https://foo.testing:6443",
+				WebConsoleURL: "https://console.foo.testing:6443",
+				ProxyConfig:   nil,
+			},
+		},
+		startResult,
+	)
+
+	stopResult, err := client.Stop()
+	assert.NoError(t, err)
+	assert.Equal(
+		t,
+		apiClient.Result{
+			Name:    "crc",
+			Success: true,
+			Error:   "",
+		},
+		stopResult,
+	)
+
+	deleteResult, err := client.Delete()
+	assert.NoError(t, err)
+	assert.Equal(
+		t,
+		apiClient.Result{
+			Name:    "crc",
+			Success: true,
+			Error:   "",
+		},
+		deleteResult,
+	)
+
+	configGetResult, err := client.GetConfig([]string{"cpus"})
+	assert.NoError(t, err)
+	assert.Equal(
+		t,
+		apiClient.GetConfigResult{
+			Error: "",
+			Configs: map[string]interface{}{
+				"cpus": float64(4),
+			},
+		},
+		configGetResult,
+	)
+
+	configSetResult, err := client.SetConfig(apiClient.SetConfigRequest{
+		Properties: map[string]interface{}{
+			"cpus": float64(5),
+		},
+	})
+	assert.NoError(t, err)
+	assert.Equal(
+		t,
+		apiClient.SetOrUnsetConfigResult{
+			Error:      "",
+			Properties: []string{"cpus"},
+		},
+		configSetResult,
+	)
+}
diff --git a/pkg/crc/api/api_test.go b/pkg/crc/api/api_test.go
index e97f8119a..67b58de27 100644
--- a/pkg/crc/api/api_test.go
+++ b/pkg/crc/api/api_test.go
@@ -8,13 +8,10 @@ import (
 	"net"
 	"os"
 	"path/filepath"
-	"strings"
 	"testing"
 
-	cmdConfig "github.com/code-ready/crc/cmd/crc/cmd/config"
-	"github.com/code-ready/crc/pkg/crc/config"
+	apiClient "github.com/code-ready/crc/pkg/crc/api/client"
 	"github.com/code-ready/crc/pkg/crc/machine/fakemachine"
-	"github.com/code-ready/crc/pkg/crc/preflight"
 	"github.com/code-ready/crc/pkg/crc/version"
 	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
@@ -31,7 +28,7 @@ func TestApi(t *testing.T) {
 	require.NoError(t, err)
 
 	client := fakemachine.NewClient()
-	api, err := createServerWithListener(listener, setupNewInMemoryConfig(), client)
+	api, err := createServerWithListener(listener, setupNewInMemoryConfig(), client, &mockLogger{})
 	require.NoError(t, err)
 	go func() {
 		if err := api.Serve(); err != nil {
@@ -118,6 +115,13 @@ func TestApi(t *testing.T) {
 				},
 			},
 		},
+		{
+			command: "logs",
+			expected: map[string]interface{}{
+				"Messages": []interface{}{"message 1", "message 2", "message 3"},
+				"Success":  true,
+			},
+		},
 	}
 	for _, test := range tt {
 		client.Failing = test.clientFailing
@@ -160,9 +164,9 @@ func TestSetconfigApi(t *testing.T) {
 	n, err := client.Read(payload)
 	assert.NoError(t, err)
 
-	var setconfigRes setOrUnsetConfigResult
+	var setconfigRes apiClient.SetOrUnsetConfigResult
 	assert.NoError(t, json.Unmarshal(payload[:n], &setconfigRes))
-	assert.Equal(t, setOrUnsetConfigResult{
+	assert.Equal(t, apiClient.SetOrUnsetConfigResult{
 		Error:      "",
 		Properties: []string{"cpus"},
 	}, setconfigRes)
@@ -186,29 +190,18 @@ func TestGetconfigApi(t *testing.T) {
 	n, err := client.Read(payload)
 	assert.NoError(t, err)
 
-	var getconfigRes getConfigResult
+	var getconfigRes apiClient.GetConfigResult
 	assert.NoError(t, json.Unmarshal(payload[:n], &getconfigRes))
 
 	configs := make(map[string]interface{})
 	configs["cpus"] = 4.0
 
-	assert.Equal(t, getConfigResult{
+	assert.Equal(t, apiClient.GetConfigResult{
 		Error:   "",
 		Configs: configs,
 	}, getconfigRes)
 }
 
-func setupNewInMemoryConfig() config.Storage {
-	storage := config.NewEmptyInMemoryStorage()
-	cfg := config.New(&skipPreflights{
-		storage: storage,
-	})
-	cmdConfig.RegisterSettings(cfg)
-	preflight.RegisterSettings(cfg)
-
-	return cfg
-}
-
 func setupAPIServer(t *testing.T) (string, func()) {
 	dir, err := ioutil.TempDir("", "api")
 	require.NoError(t, err)
@@ -218,7 +211,7 @@ func setupAPIServer(t *testing.T) (string, func()) {
 	require.NoError(t, err)
 
 	client := fakemachine.NewClient()
-	api, err := createServerWithListener(listener, setupNewInMemoryConfig(), client)
+	api, err := createServerWithListener(listener, setupNewInMemoryConfig(), client, &mockLogger{})
 	require.NoError(t, err)
 	go func() {
 		if err := api.Serve(); err != nil {
@@ -229,21 +222,9 @@ func setupAPIServer(t *testing.T) (string, func()) {
 	return socket, func() { os.RemoveAll(dir) }
 }
 
-type skipPreflights struct {
-	storage config.RawStorage
-}
-
-func (s *skipPreflights) Get(key string) interface{} {
-	if strings.HasPrefix(key, "skip-") {
-		return "true"
-	}
-	return s.storage.Get(key)
-}
-
-func (s *skipPreflights) Set(key string, value interface{}) error {
-	return s.storage.Set(key, value)
+type mockLogger struct {
 }
 
-func (s *skipPreflights) Unset(key string) error {
-	return s.storage.Unset(key)
+func (*mockLogger) Messages() []string {
+	return []string{"message 1", "message 2", "message 3"}
 }
diff --git a/pkg/crc/api/apitest.go b/pkg/crc/api/apitest.go
new file mode 100644
index 000000000..f28a6996b
--- /dev/null
+++ b/pkg/crc/api/apitest.go
@@ -0,0 +1,39 @@
+package api
+
+import (
+	"strings"
+
+	cmdConfig "github.com/code-ready/crc/cmd/crc/cmd/config"
+	"github.com/code-ready/crc/pkg/crc/config"
+	"github.com/code-ready/crc/pkg/crc/preflight"
+)
+
+func setupNewInMemoryConfig() config.Storage {
+	storage := config.NewEmptyInMemoryStorage()
+	cfg := config.New(&skipPreflights{
+		storage: storage,
+	})
+	cmdConfig.RegisterSettings(cfg)
+	preflight.RegisterSettings(cfg)
+
+	return cfg
+}
+
+type skipPreflights struct {
+	storage config.RawStorage
+}
+
+func (s *skipPreflights) Get(key string) interface{} {
+	if strings.HasPrefix(key, "skip-") {
+		return "true"
+	}
+	return s.storage.Get(key)
+}
+
+func (s *skipPreflights) Set(key string, value interface{}) error {
+	return s.storage.Set(key, value)
+}
+
+func (s *skipPreflights) Unset(key string) error {
+	return s.storage.Unset(key)
+}
diff --git a/pkg/crc/logging/inmemory_test.go b/pkg/crc/logging/inmemory_test.go
new file mode 100644
index 000000000..3061878aa
--- /dev/null
+++ b/pkg/crc/logging/inmemory_test.go
@@ -0,0 +1,23 @@
+package logging
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAddLogs(t *testing.T) {
+	memory := newInMemoryHook(5)
+
+	assert.Len(t, memory.Messages(), 0)
+
+	for i := 1; i < 10; i++ {
+		assert.NoError(t, memory.Fire(&logrus.Entry{
+			Message: fmt.Sprintf("message %d", i),
+		}))
+	}
+
+	assert.Equal(t, []string{"message 5", "message 6", "message 7", "message 8", "message 9"}, memory.Messages())
+}
diff --git a/pkg/crc/preflight/preflight_darwin_test.go b/pkg/crc/preflight/preflight_darwin_test.go
index a56f7ac31..214b967ce 100644
--- a/pkg/crc/preflight/preflight_darwin_test.go
+++ b/pkg/crc/preflight/preflight_darwin_test.go
@@ -23,8 +23,8 @@ func TestCountPreflights(t *testing.T) {
 	assert.Len(t, getPreflightChecks(true, true, network.VSockMode), 19)
 
 	assert.Len(t, getPreflightChecks(true, false, network.VSockMode), 15)
-	assert.Len(t, getPreflightChecks(false, true, network.VSockMode), 19)
+	assert.Len(t, getPreflightChecks(false, true, network.VSockMode), 15)
 
-	assert.Len(t, getPreflightChecks(false, true, network.DefaultMode), 20)
+	assert.Len(t, getPreflightChecks(false, true, network.DefaultMode), 14)
 	assert.Len(t, getPreflightChecks(true, false, network.DefaultMode), 14)
 }