Repository: code-ready/crc
Pull request #2056 (2021-03-03 16:58:20)

Note systemd-resolved usage for RHEL 8
systemd-resolved can be used on RHEL 8.3 and compatible distributions as it is available as a Tech Preview. However, there are some configurations that need to be made for this system to actually work with present releases.

This is a documentation pull request, it does not alter functionality of CRC itself. I have personally been running with this setup on RHEL 8 for several days now with no obvious issues.

centos-ci: Can one of the admins verify this patch?
openshift-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/code-ready/crc/pull/2056#" title="Author self-approved">omenos</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **robin-owen** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @robin-owen` in a comment when ready.

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

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

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

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":["robin-owen"]} -->
openshift-ci-robot: Hi @omenos. Thanks for your PR.

I'm waiting for a [code-ready](https://github.com/orgs/code-ready/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/code-ready/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=code-ready%2Fcrc).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

cfergeau: /ok-to-test
cfergeau: Thanks for the patch!

> systemd-resolved can be used on RHEL 8.3 and compatible distributions as it is available as a Tech Preview. However, there are some configurations that need to be made for this system to actually work with present releases.
> 
> This is a documentation pull request, it does not alter functionality of CRC itself. I have personally been running with this setup on RHEL 8 for several days now with no obvious issues.

Can you clarify "can be used"? Does this mean the system administrator can manually configure their rhel system to use systemd-resolved? Or is this the default after doing an install? 
omenos: @cfergeau I've updated the notes to *hopefully* clarify things a bit more about the state of systemd-resolved. Would it be a good idea to link to the RHEL docs?
cfergeau: > @cfergeau I've updated the notes to _hopefully_ clarify things a bit more about the state of systemd-resolved in RHEL 8. Would it be a good idea to link to the RHEL docs?
> 
> It is a Tech Preview, and does need to be configured by the user as it is not a default configuration.

Thanks for clarifying. A link to RHEL docs would not hurt, though I'll let @robin-owen weight on this.
Ideally `crc` would detect whether NM or system-resolved or ... are in use, rather than hardcoding what is used depending on the distro it detects. But this is not there yet, hence the need for this kind of documentation :)
omenos: Yes, if I had the technical skill right now this pull request would have been for replacing the hard-coding with detection, but I'm not quite there yet with my gophering :wink: 
robin-owen: I just saw the comments about linking to the RHEL documentation. I don't see an issue with this one way or the other, honestly, so I'm perfectly happy whether we include that or not. :)
omenos: Updated to include the text revisions and variable substitutions. I should have looked at the product-attributes file containing the macros, will keep that in mind for the future.
openshift-ci[bot]: @omenos: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/e2e-crc | fc8a7f201b6f75cf49b2b021531dafdfff1a7cc9 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2056/pull-ci-code-ready-crc-master-e2e-crc/1367947351175467008) | `/test e2e-crc`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=2056). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:omenos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 50f62acc3..9cbad1733 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -23,7 +23,7 @@ jobs:
         command: sudo rm -rf /usr/local/go
     - run:
         name: Install go
-        command: curl https://dl.google.com/go/go${GOVERSION}.darwin-amd64.tar.gz | sudo tar -C /usr/local -xz
+        command: curl --http1.1 https://dl.google.com/go/go${GOVERSION}.darwin-amd64.tar.gz | sudo tar -C /usr/local -xz
     - run:
         name: List go version
         command: go version
@@ -69,6 +69,9 @@ jobs:
       - run:
           name: mock make release
           command: make BUNDLE_DIR=/tmp MOCK_BUNDLE=true release
+      - run:
+          name: make rpm
+          command: CONTAINER_RUNTIME=docker make test-rpmbuild
 
   build_docs:
     machine:
diff --git a/Makefile b/Makefile
index ea3d37c7c..c08272988 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
 SHELL := /bin/bash
 
-BUNDLE_VERSION = 4.7.0
+BUNDLE_VERSION = 4.7.2
 BUNDLE_EXTENSION = crcbundle
-CRC_VERSION = 1.23.0
+CRC_VERSION = 1.23.1
 COMMIT_SHA=$(shell git rev-parse --short HEAD)
 MACOS_INSTALL_PATH = /Library/CodeReady Containers
 CONTAINER_RUNTIME ?= podman
@@ -59,7 +59,7 @@ ifdef OKD_VERSION
 endif
 
 # https://golang.org/cmd/link/
-LDFLAGS := $(VERSION_VARIABLES) -extldflags='-static' -s -w
+LDFLAGS := $(VERSION_VARIABLES) -extldflags='-static' ${GO_EXTRA_LDFLAGS}
 
 # Add default target
 .PHONY: default
@@ -82,19 +82,19 @@ check: cross build_e2e $(HOST_BUILD_DIR)/crc-embedder test cross-lint vendorchec
 
 .PHONY: install
 install: $(SOURCES)
-	go install -ldflags="$(LDFLAGS)" ./cmd/crc
+	go install -ldflags="$(LDFLAGS)" $(GO_EXTRA_BUILDFLAGS) ./cmd/crc
 
 $(BUILD_DIR)/macos-amd64/crc: $(SOURCES)
-	GOARCH=amd64 GOOS=darwin go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/macos-amd64/crc ./cmd/crc
+	GOARCH=amd64 GOOS=darwin go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/macos-amd64/crc $(GO_EXTRA_BUILDFLAGS) ./cmd/crc
 
 $(BUILD_DIR)/linux-amd64/crc: $(SOURCES)
-	GOOS=linux GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/linux-amd64/crc ./cmd/crc
+	GOOS=linux GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/linux-amd64/crc $(GO_EXTRA_BUILDFLAGS) ./cmd/crc
 
 $(BUILD_DIR)/windows-amd64/crc.exe: $(SOURCES)
-	GOARCH=amd64 GOOS=windows go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/windows-amd64/crc.exe ./cmd/crc
+	GOARCH=amd64 GOOS=windows go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/windows-amd64/crc.exe $(GO_EXTRA_BUILDFLAGS) ./cmd/crc
 
 $(HOST_BUILD_DIR)/crc-embedder: $(SOURCES)
-	go build --tags="build" -ldflags="$(LDFLAGS)" -o $(HOST_BUILD_DIR)/crc-embedder ./cmd/crc-embedder
+	go build --tags="build" -ldflags="$(LDFLAGS)" -o $(HOST_BUILD_DIR)/crc-embedder $(GO_EXTRA_BUILDFLAGS) ./cmd/crc-embedder
 
 .PHONY: cross ## Cross compiles all binaries
 cross: $(BUILD_DIR)/macos-amd64/crc $(BUILD_DIR)/linux-amd64/crc $(BUILD_DIR)/windows-amd64/crc.exe
@@ -111,6 +111,14 @@ containerized: clean
 test:
 	go test -race --tags build -v -ldflags="$(VERSION_VARIABLES)" ./pkg/... ./cmd/...
 
+.PHONY: spec test-rpmbuild
+
+GENERATED_RPM_FILES=packaging/rpm/crc.spec images/rpmbuild/Containerfile
+spec: $(GENERATED_RPM_FILES)
+	
+test-rpmbuild: spec
+	${CONTAINER_RUNTIME} build -f images/rpmbuild/Containerfile .
+
 .PHONY: build_docs
 build_docs:
 	${CONTAINER_RUNTIME} run -v $(CURDIR)/docs:/docs:Z --rm $(DOCS_BUILD_CONTAINER) build_docs -b html5 -D /$(DOCS_BUILD_DIR) -o index.html $(DOCS_BUILD_TARGET)
@@ -139,6 +147,7 @@ clean_macos_package:
 
 .PHONY: clean ## Remove all build artifacts
 clean: clean_docs clean_macos_package
+	rm -f $(GENERATED_RPM_FILES)
 	rm -rf $(BUILD_DIR)
 	rm -f $(GOPATH)/bin/crc
 	rm -rf $(RELEASE_DIR)
@@ -180,7 +189,7 @@ fmt:
 	@gofmt -l -w $(SOURCE_DIRS)
 
 $(GOPATH)/bin/golangci-lint:
-	pushd /tmp && GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.36.0 && popd
+	pushd /tmp && GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.37.1 && popd
 
 # Run golangci-lint against code
 .PHONY: lint cross-lint
@@ -266,3 +275,18 @@ $(BUILD_DIR)/macos-amd64/crc-macos-amd64.pkg: packagedir
 
 $(BUILD_DIR)/macos-amd64/crc-installer.tar: packagedir
 	tar -cvf $(BUILD_DIR)/macos-amd64/crc-installer.tar ./packaging
+
+$(GOPATH)/bin/gomod2rpmdeps:
+	pushd /tmp && GO111MODULE=on go get github.com/cfergeau/gomod2rpmdeps/cmd/gomod2rpmdeps && popd
+
+%.spec: %.spec.in $(GOPATH)/bin/gomod2rpmdeps
+	@$(GOPATH)/bin/gomod2rpmdeps | sed -e '/__BUNDLED_REQUIRES__/r /dev/stdin' \
+					   -e '/__BUNDLED_REQUIRES__/d' \
+					   -e 's/__VERSION__/'$(CRC_VERSION)'/g' \
+					   -e 's/__OPENSHIFT_VERSION__/'$(BUNDLE_VERSION)'/g' \
+				       $< >$@
+
+%: %.in
+	@sed -e 's/__VERSION__/'$(CRC_VERSION)'/g' \
+	     -e 's/__OPENSHIFT_VERSION__/'$(BUNDLE_VERSION)'/g' \
+	     $< >$@
diff --git a/centos_ci.sh b/centos_ci.sh
index b9faf3ac3..e5682be2f 100644
--- a/centos_ci.sh
+++ b/centos_ci.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 # bundle location
-BUNDLE_VERSION=4.7.0
+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 4d5ad8f6f..b27cc2e5e 100644
--- a/cmd/crc/cmd/config/config.go
+++ b/cmd/crc/cmd/config/config.go
@@ -82,7 +82,7 @@ func RegisterSettings(cfg *config.Config) {
 	cfg.AddSetting(NetworkMode, string(network.DefaultMode), network.ValidateMode, network.SuccessfullyAppliedMode,
 		"Network mode (default or vsock)")
 	cfg.AddSetting(HostNetworkAccess, false, validateHostNetworkAccess, config.SuccessfullyApplied,
-		"Allow TCP/IP connections from the CodeReday Containers VM to services running on the host (true/false, default: false)")
+		"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,
 		"Automatically start the tray (true/false, default: true)")
diff --git a/cmd/crc/cmd/daemon.go b/cmd/crc/cmd/daemon.go
index b7b15dbcb..a5e7bea32 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"
@@ -162,7 +163,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/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/docs/source/topics/ref_minimum-system-requirements.adoc b/docs/source/topics/ref_minimum-system-requirements.adoc
index e5aeaa1a8..9c5e721c7 100644
--- a/docs/source/topics/ref_minimum-system-requirements.adoc
+++ b/docs/source/topics/ref_minimum-system-requirements.adoc
@@ -32,7 +32,7 @@ To assign more resources to the {prod} virtual machine, see link:{crc-gsg-url}#c
 
 === {mac}
 
-* On {mac}, {prod} requires macOS 10.12 Sierra or newer.
+* On {mac}, {prod} requires macOS 10.14 Mojave or newer.
 {prod} does not work on earlier versions of {mac}.
 
 === Linux
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/images/rpmbuild/.gitignore b/images/rpmbuild/.gitignore
new file mode 100644
index 000000000..bbcd7072f
--- /dev/null
+++ b/images/rpmbuild/.gitignore
@@ -0,0 +1,1 @@
+Containerfile
diff --git a/images/rpmbuild/Containerfile.in b/images/rpmbuild/Containerfile.in
new file mode 100644
index 000000000..8aecb0d39
--- /dev/null
+++ b/images/rpmbuild/Containerfile.in
@@ -0,0 +1,9 @@
+FROM registry.centos.org/centos
+WORKDIR $APP_ROOT/src
+RUN yum -y install git-core rpm-build dnf-plugins-core 'dnf-command(builddep)'
+COPY . .
+RUN mkdir -p ~/rpmbuild/SOURCES/ && \
+    git archive --format=tar --prefix=crc-__VERSION__-__OPENSHIFT_VERSION__/ HEAD | gzip >~/rpmbuild/SOURCES/crc-__VERSION__.tar.gz
+RUN yum config-manager --set-enabled powertools && \
+    yum -y builddep packaging/rpm/crc.spec && \
+    rpmbuild -bb -v packaging/rpm/crc.spec
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/packaging/rpm/.gitignore b/packaging/rpm/.gitignore
new file mode 100644
index 000000000..c0f116255
--- /dev/null
+++ b/packaging/rpm/.gitignore
@@ -0,0 +1,1 @@
+crc.spec
diff --git a/packaging/rpm/crc.spec.in b/packaging/rpm/crc.spec.in
new file mode 100644
index 000000000..3f07156db
--- /dev/null
+++ b/packaging/rpm/crc.spec.in
@@ -0,0 +1,90 @@
+# https://github.com/code-ready/admin-helper
+%global goipath         github.com/code-ready/crc
+Version:                __VERSION__
+%global 	 	openshift_suffix -__OPENSHIFT_VERSION__
+
+%gometa
+
+# debuginfo is not supported on RHEL with Go packages
+%global debug_package %{nil}
+%global _enable_debug_package 0
+
+%global common_description %{expand:
+CodeReady Container's executable}
+
+
+%global golicenses    LICENSE
+%global godocs        *.md
+
+
+Name:           %{goname}
+Release:        1%{?dist}
+Summary:        CodeReady Container's executable
+License:        APL 2.0
+ExcludeArch:    armv7hl i686
+URL:            %{gourl}
+ExcludeArch:    s390x
+Source0:        %{gosource}
+
+#generate_buildrequires
+#go_generate_buildrequires
+
+BuildRequires: git-core
+BuildRequires: go-srpm-macros
+BuildRequires: make
+
+__BUNDLED_REQUIRES__
+
+%description
+%{common_description}
+
+%gopkg
+
+%prep
+# order of these 3 steps is important, build breaks if they are moved around
+%global archivename crc-%{version}%{?openshift_suffix}
+%autosetup -S git -n crc-%{version}%{?openshift_suffix}
+# with fedora macros: goprep -e -k
+install -m 0755 -vd "$(dirname %{gobuilddir}/src/%{goipath})"
+ln -fs "$(pwd)" "%{gobuilddir}/src/%{goipath}"
+
+
+%build
+export GOFLAGS="-mod=vendor"
+make GO_EXTRA_LDFLAGS="-B 0x$(head -c20 /dev/urandom|od -An -tx1|tr -d ' \n')" GO_EXTRA_BUILDFLAGS="" cross
+
+%install
+# with fedora macros: gopkginstall
+install -m 0755 -vd                     %{buildroot}%{_bindir}
+install -m 0755 -vp %{gobuilddir}/src/%{goipath}/out/linux-amd64/crc %{buildroot}%{_bindir}/
+
+install -d %{buildroot}%{_datadir}/%{name}-redistributable/{linux,macos,windows}
+install -m 0755 -vp %{gobuilddir}/src/%{goipath}/out/linux-amd64/crc %{buildroot}%{_datadir}/%{name}-redistributable/linux/
+install -m 0755 -vp %{gobuilddir}/src/%{goipath}/out/windows-amd64/crc.exe %{buildroot}%{_datadir}/%{name}-redistributable/windows/
+install -m 0755 -vp %{gobuilddir}/src/%{goipath}/out/macos-amd64/crc %{buildroot}%{_datadir}/%{name}-redistributable/macos/
+
+%check
+# with fedora macros: gocheck
+export GOFLAGS="-mod=vendor"
+# crc uses `go test -race`, which triggers gvisor issues on ppc64le:
+# vendor/gvisor.dev/gvisor/pkg/sync/race_unsafe.go:47:6: missing function body
+# and a ppc64le implementation is indeed missing on ppc64le:
+# https://github.com/google/gvisor/blob/master/pkg/sync/race_unsafe.go
+# https://github.com/google/gvisor/tree/master/pkg/sync
+%ifnarch ppc64le
+make test
+%endif
+
+%files
+%license %{golicenses}
+%doc
+%{_bindir}/*
+%{_datadir}/%{name}-redistributable/linux/*
+%{_datadir}/%{name}-redistributable/macos/*
+%{_datadir}/%{name}-redistributable/windows/*
+
+#gopkgfiles
+
+%changelog
+* Mon Feb 15 2021 Christophe Fergeau <cfergeau@redhat.com> - 1.22.0-1
+- Initial import in Fedora
diff --git a/pkg/crc/adminhelper/hosts.go b/pkg/crc/adminhelper/hosts.go
index e6845a5ac..a98670964 100644
--- a/pkg/crc/adminhelper/hosts.go
+++ b/pkg/crc/adminhelper/hosts.go
@@ -7,7 +7,7 @@ import (
 )
 
 var (
-	goodhostPath = filepath.Join(constants.CrcBinDir, constants.AdminHelperExecutableName)
+	adminHelperPath = filepath.Join(constants.CrcBinDir, constants.AdminHelperExecutableName)
 )
 
 // UpdateHostsFile updates the host's /etc/hosts file with Instance IP.
diff --git a/pkg/crc/adminhelper/hosts_nowin.go b/pkg/crc/adminhelper/hosts_nowin.go
index fc561b28e..feb2a2e2a 100644
--- a/pkg/crc/adminhelper/hosts_nowin.go
+++ b/pkg/crc/adminhelper/hosts_nowin.go
@@ -7,6 +7,6 @@ import (
 )
 
 func execute(args ...string) error {
-	_, _, err := crcos.RunWithDefaultLocale(goodhostPath, args...)
+	_, _, err := crcos.RunWithDefaultLocale(adminHelperPath, args...)
 	return err
 }
diff --git a/pkg/crc/adminhelper/hosts_windows.go b/pkg/crc/adminhelper/hosts_windows.go
index ba6a7fb1e..54fac5255 100644
--- a/pkg/crc/adminhelper/hosts_windows.go
+++ b/pkg/crc/adminhelper/hosts_windows.go
@@ -7,6 +7,6 @@ import (
 )
 
 func execute(args ...string) error {
-	_, _, err := powershell.ExecuteAsAdmin("modifying hosts file", strings.Join(append([]string{goodhostPath}, args...), " "))
+	_, _, err := powershell.ExecuteAsAdmin("modifying hosts file", strings.Join(append([]string{adminHelperPath}, args...), " "))
 	return err
 }
diff --git a/pkg/crc/api/adapter.go b/pkg/crc/api/adapter.go
index 37cf6fb9f..1334d592c 100644
--- a/pkg/crc/api/adapter.go
+++ b/pkg/crc/api/adapter.go
@@ -5,6 +5,7 @@ import (
 
 	"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 {
@@ -12,7 +13,7 @@ type AdaptedClient interface {
 
 	Delete() Result
 	GetConsoleURL() ConsoleResult
-	Start(startConfig machine.StartConfig) StartResult
+	Start(ctx context.Context, startConfig machine.StartConfig) StartResult
 	Status() ClusterStatusResult
 	Stop() Result
 }
@@ -87,8 +88,8 @@ func (a *Adapter) GetConsoleURL() ConsoleResult {
 	}
 }
 
-func (a *Adapter) Start(startConfig machine.StartConfig) StartResult {
-	res, err := a.Underlying.Start(context.Background(), startConfig)
+func (a *Adapter) Start(ctx context.Context, startConfig machine.StartConfig) StartResult {
+	res, err := a.Underlying.Start(ctx, startConfig)
 	if err != nil {
 		logging.Error(err)
 		return StartResult{
@@ -126,13 +127,19 @@ func (a *Adapter) Status() ClusterStatusResult {
 }
 
 func (a *Adapter) Stop() Result {
-	_, err := a.Underlying.Stop()
+	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 Result{
+					Name:    a.Underlying.GetName(),
+					Success: false,
+					Error:   err.Error(),
+				}
+			}
 		}
 	}
 	return Result{
diff --git a/pkg/crc/api/api.go b/pkg/crc/api/api.go
index 76ed78f6c..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))
 	}
@@ -90,7 +93,7 @@ func (api Server) handleConnections(conn net.Conn) {
 		logging.Error("Error reading from socket")
 		return
 	}
-	logging.Debug("Received Request:", string(inBuffer[0:numBytes]))
+	logging.Debugf("rpc:%p: Received request: %s", conn, string(inBuffer[0:numBytes]))
 	err = json.Unmarshal(inBuffer[0:numBytes], &req)
 	if err != nil {
 		logging.Error("Error decoding request: ", err.Error())
@@ -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:
@@ -125,6 +128,8 @@ func (api Server) handleConnections(conn net.Conn) {
 }
 
 func writeStringToSocket(socket net.Conn, msg string) {
+	logging.Debugf("rpc:%p: Sending answer: %s", socket, msg)
+
 	var outBuffer bytes.Buffer
 	_, err := outBuffer.WriteString(msg)
 	if err != nil {
diff --git a/pkg/crc/api/handlers.go b/pkg/crc/api/handlers.go
index c0ef8b720..5eb57df20 100644
--- a/pkg/crc/api/handlers.go
+++ b/pkg/crc/api/handlers.go
@@ -2,6 +2,7 @@ package api
 
 import (
 	"bytes"
+	"context"
 	"encoding/json"
 	"fmt"
 
@@ -16,10 +17,22 @@ 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 (h *Handler) Status() string {
 	clusterStatus := h.MachineClient.Status()
 	return encodeStructToJSON(clusterStatus)
@@ -52,7 +65,7 @@ func (h *Handler) Start(args json.RawMessage) string {
 	}
 
 	startConfig := getStartConfig(h.Config, parsedArgs)
-	status := h.MachineClient.Start(startConfig)
+	status := h.MachineClient.Start(context.Background(), startConfig)
 	return encodeStructToJSON(status)
 }
 
@@ -221,8 +234,9 @@ func encodeStructToJSON(v interface{}) string {
 	s, err := json.Marshal(v)
 	if err != nil {
 		logging.Error(err.Error())
-		err := commandError{
-			Err: "Failed while encoding JSON to string",
+		err := Result{
+			Success: false,
+			Error:   "Failed while encoding JSON to string",
 		}
 		s, _ := json.Marshal(err)
 		return string(s)
@@ -231,8 +245,9 @@ func encodeStructToJSON(v interface{}) string {
 }
 
 func encodeErrorToJSON(errMsg string) string {
-	err := commandError{
-		Err: errMsg,
+	err := Result{
+		Success: false,
+		Error:   errMsg,
 	}
 	return encodeStructToJSON(err)
 }
diff --git a/pkg/crc/api/types.go b/pkg/crc/api/types.go
index b26e79098..4909dfc29 100644
--- a/pkg/crc/api/types.go
+++ b/pkg/crc/api/types.go
@@ -5,10 +5,6 @@ import (
 	"net"
 )
 
-type commandError struct {
-	Err string
-}
-
 type Server struct {
 	handler                RequestHandler
 	listener               net.Listener
@@ -25,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
@@ -56,3 +53,8 @@ type getConfigResult struct {
 type startArgs struct {
 	PullSecretFile string `json:"pullSecretFile"`
 }
+
+type loggerResult struct {
+	Success  bool
+	Messages []string
+}
diff --git a/pkg/crc/cluster/cluster.go b/pkg/crc/cluster/cluster.go
index 6ff504175..d8bb9e942 100644
--- a/pkg/crc/cluster/cluster.go
+++ b/pkg/crc/cluster/cluster.go
@@ -1,6 +1,7 @@
 package cluster
 
 import (
+	"context"
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
@@ -21,7 +22,7 @@ import (
 // #nosec G101
 const vmPullSecretPath = "/var/lib/kubelet/config.json"
 
-func WaitForSSH(sshRunner *ssh.Runner) error {
+func WaitForSSH(ctx context.Context, sshRunner *ssh.Runner) error {
 	checkSSHConnectivity := func() error {
 		_, _, err := sshRunner.Run("exit 0")
 		if err != nil {
@@ -30,7 +31,7 @@ func WaitForSSH(sshRunner *ssh.Runner) error {
 		return nil
 	}
 
-	return errors.RetryAfter(300*time.Second, checkSSHConnectivity, time.Second)
+	return errors.RetryAfterWithContext(ctx, 300*time.Second, checkSSHConnectivity, time.Second)
 }
 
 const (
@@ -309,7 +310,7 @@ func WaitForRequestHeaderClientCaFile(sshRunner *ssh.Runner) error {
 	return errors.RetryAfter(8*time.Minute, lookupRequestHeaderClientCa, 2*time.Second)
 }
 
-func WaitForAPIServer(ocConfig oc.Config) error {
+func WaitForAPIServer(ctx context.Context, ocConfig oc.Config) error {
 	logging.Info("Waiting for kube-apiserver availability... [takes around 2min]")
 	waitForAPIServer := func() error {
 		stdout, stderr, err := ocConfig.WithFailFast().RunOcCommand("get", "nodes")
@@ -320,7 +321,7 @@ func WaitForAPIServer(ocConfig oc.Config) error {
 		logging.Debug(stdout)
 		return nil
 	}
-	return errors.RetryAfter(4*time.Minute, waitForAPIServer, time.Second)
+	return errors.RetryAfterWithContext(ctx, 4*time.Minute, waitForAPIServer, time.Second)
 }
 
 func DeleteOpenshiftAPIServerPods(ocConfig oc.Config) error {
diff --git a/pkg/crc/cluster/clusteroperator.go b/pkg/crc/cluster/clusteroperator.go
index f1ec234b7..594f34400 100644
--- a/pkg/crc/cluster/clusteroperator.go
+++ b/pkg/crc/cluster/clusteroperator.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"sort"
 	"strings"
 
 	"github.com/code-ready/crc/pkg/crc/logging"
@@ -25,23 +26,25 @@ type Status struct {
 	unavailable []string
 }
 
+const maxNames = 5
+
 func (status *Status) String() string {
 	if len(status.progressing) == 1 {
 		return fmt.Sprintf("Operator %s is progressing", status.progressing[0])
 	} else if len(status.progressing) > 1 {
-		return fmt.Sprintf("%d operators are progressing: %s", len(status.progressing), strings.Join(status.progressing, ", "))
+		return fmt.Sprintf("%d operators are progressing: %s", len(status.progressing), joinWithLimit(status.progressing, maxNames))
 	}
 
 	if len(status.degraded) == 1 {
 		return fmt.Sprintf("Operator %s is degraded", status.degraded[0])
 	} else if len(status.degraded) > 0 {
-		return fmt.Sprintf("%d operators are degraded: %s", len(status.degraded), strings.Join(status.degraded, ", "))
+		return fmt.Sprintf("%d operators are degraded: %s", len(status.degraded), joinWithLimit(status.degraded, maxNames))
 	}
 
 	if len(status.unavailable) == 1 {
 		return fmt.Sprintf("Operator %s is not yet available", status.unavailable[0])
 	} else if len(status.unavailable) > 0 {
-		return fmt.Sprintf("%d operators are not available: %s", len(status.unavailable), strings.Join(status.unavailable, ", "))
+		return fmt.Sprintf("%d operators are not available: %s", len(status.unavailable), joinWithLimit(status.unavailable, maxNames))
 	}
 
 	if status.IsReady() {
@@ -50,6 +53,22 @@ func (status *Status) String() string {
 	return "Operators are not ready yet"
 }
 
+func joinWithLimit(names []string, maxNames int) string {
+	sort.Strings(names)
+	ret := strings.Join(names[0:min(len(names), maxNames)], ", ")
+	if len(names) > maxNames {
+		ret += ", ..."
+	}
+	return ret
+}
+
+func min(a, b int) int {
+	if a > b {
+		return b
+	}
+	return a
+}
+
 func (status *Status) IsReady() bool {
 	return status.Available && !status.Progressing && !status.Degraded && !status.Disabled
 }
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/cluster/monitoring.go b/pkg/crc/cluster/monitoring.go
index 89996e09d..37172ba18 100644
--- a/pkg/crc/cluster/monitoring.go
+++ b/pkg/crc/cluster/monitoring.go
@@ -20,20 +20,28 @@ func StartMonitoring(ocConfig oc.Config) error {
 		return err
 	}
 
+	indexForClusterMonitoringDeploymentKind := getIndexInOverridesForObjectName(cv, "cluster-monitoring-operator")
+	indexForClusterMonitoringCVOKind := getIndexInOverridesForObjectName(cv, "monitoring")
+
+	if indexForClusterMonitoringDeploymentKind != -1 && indexForClusterMonitoringCVOKind != -1 {
+		_, _, err = ocConfig.RunOcCommand("patch", "clusterversion/version",
+			"--type", "json",
+			"--patch", fmt.Sprintf(`'[{"op":"remove", "path":"/spec/overrides/%d"},{"op":"remove", "path":"/spec/overrides/%d"}]'`,
+				indexForClusterMonitoringDeploymentKind, indexForClusterMonitoringCVOKind-1))
+	}
+	return err
+}
+
+func getIndexInOverridesForObjectName(cv v1.ClusterVersion, objectName string) int {
 	pos := -1
 	for i, override := range cv.Spec.Overrides {
-		if override.Name == "cluster-monitoring-operator" {
+		if override.Name == objectName {
 			pos = i
 			break
 		}
 	}
 	if pos == -1 {
-		log.Debug("monitoring operator not found in cluster version overrides")
-		return nil
+		log.Debugf("%s not found in cluster version overrides", objectName)
 	}
-
-	_, _, err = ocConfig.RunOcCommand("patch", "clusterversion/version",
-		"--type", "json",
-		"--patch", fmt.Sprintf(`'[{"op":"remove", "path":"/spec/overrides/%d"}]'`, pos))
-	return err
+	return pos
 }
diff --git a/pkg/crc/cluster/status.go b/pkg/crc/cluster/status.go
index 9e54c9ef5..32f521bed 100644
--- a/pkg/crc/cluster/status.go
+++ b/pkg/crc/cluster/status.go
@@ -1,6 +1,7 @@
 package cluster
 
 import (
+	"context"
 	"fmt"
 	"time"
 
@@ -9,7 +10,11 @@ import (
 )
 
 // WaitForClusterStable checks that the cluster is running a number of consecutive times
-func WaitForClusterStable(ocConfig oc.Config, monitoringEnabled bool) error {
+func WaitForClusterStable(ctx context.Context, ocConfig oc.Config, monitoringEnabled bool) error {
+	if ctx.Err() != nil {
+		return ctx.Err()
+	}
+
 	startTime := time.Now()
 
 	retryDuration := 30 * time.Second
@@ -41,7 +46,11 @@ func WaitForClusterStable(ocConfig oc.Config, monitoringEnabled bool) error {
 		} else {
 			count = 0
 		}
-		time.Sleep(retryDuration)
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case <-time.After(retryDuration):
+		}
 	}
 
 	return fmt.Errorf("cluster operators are still not stable after %s", time.Since(startTime))
diff --git a/pkg/crc/constants/constants.go b/pkg/crc/constants/constants.go
index de9dbef08..a76e0c20d 100644
--- a/pkg/crc/constants/constants.go
+++ b/pkg/crc/constants/constants.go
@@ -21,15 +21,13 @@ const (
 
 	CrcEnvPrefix = "CRC"
 
-	DefaultWebConsoleURL      = "https://console-openshift-console.apps-crc.testing"
-	DefaultAPIURL             = "https://api.crc.testing:6443"
 	DefaultLogLevel           = "info"
 	ConfigFile                = "crc.json"
 	LogFile                   = "crc.log"
 	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"
diff --git a/pkg/crc/errors/multierror.go b/pkg/crc/errors/multierror.go
index ea917c9f5..35fb46937 100644
--- a/pkg/crc/errors/multierror.go
+++ b/pkg/crc/errors/multierror.go
@@ -1,6 +1,7 @@
 package errors
 
 import (
+	"context"
 	"fmt"
 	"strings"
 	"time"
@@ -80,8 +81,12 @@ func (r RetriableError) Error() string {
 	return "Temporary error: " + r.Err.Error()
 }
 
-// RetryAfter retries for a certain duration, after a delay
-func RetryAfter(limit time.Duration, callback func() error, d time.Duration) error {
+// RetryAfterWithContext retries for a certain duration, after a delay
+func RetryAfterWithContext(ctx context.Context, limit time.Duration, callback func() error, d time.Duration) error {
+	if ctx.Err() != nil {
+		return ctx.Err()
+	}
+
 	m := MultiError{}
 	timeLimit := time.Now().Add(limit)
 	attempt := 0
@@ -98,8 +103,17 @@ func RetryAfter(limit time.Duration, callback func() error, d time.Duration) err
 			return m
 		}
 		logging.Debugf("error: %v - sleeping %s", err, d)
-		time.Sleep(d)
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case <-time.After(d):
+		}
 	}
 	logging.Debugf("RetryAfter timeout after %d tries", attempt)
 	return m
 }
+
+// RetryAfter retries for a certain duration, after a delay
+func RetryAfter(limit time.Duration, callback func() error, d time.Duration) error {
+	return RetryAfterWithContext(context.Background(), limit, callback, d)
+}
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..8645dc33d 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) {
@@ -53,6 +54,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 52d20078d..e4f874178 100644
--- a/pkg/crc/machine/bundle/metadata.go
+++ b/pkg/crc/machine/bundle/metadata.go
@@ -23,6 +23,7 @@ type CrcBundleInfo struct {
 	Storage     Storage     `json:"storage"`
 	DriverInfo  DriverInfo  `json:"driverInfo"`
 	cachedPath  string
+	bundleName  string
 }
 
 type BuildInfo struct {
@@ -71,6 +72,10 @@ func (bundle *CrcBundleInfo) resolvePath(filename string) string {
 	return filepath.Join(bundle.cachedPath, filename)
 }
 
+func (bundle *CrcBundleInfo) GetBundleName() string {
+	return bundle.bundleName
+}
+
 func (bundle *CrcBundleInfo) GetAPIHostname() string {
 	return fmt.Sprintf("api.%s.%s", bundle.ClusterInfo.ClusterName, bundle.ClusterInfo.BaseDomain)
 }
@@ -106,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 fc44b73f6..135195128 100644
--- a/pkg/crc/machine/bundle/repository.go
+++ b/pkg/crc/machine/bundle/repository.go
@@ -45,9 +45,17 @@ func (repo *Repository) Get(bundleName string) (*CrcBundleInfo, error) {
 		return nil, err
 	}
 	bundleInfo.cachedPath = path
+	bundleInfo.bundleName = bundleName
+
 	if err := bundleInfo.verify(); err != nil {
 		return nil, err
 	}
+	if fmt.Sprintf(".%s", bundleInfo.ClusterInfo.AppsDomain) != constants.AppsDomain {
+		return nil, fmt.Errorf("unexpected bundle, it must have %s apps domain", constants.AppsDomain)
+	}
+	if bundleInfo.GetAPIHostname() != fmt.Sprintf("api%s", constants.ClusterDomain) {
+		return nil, fmt.Errorf("unexpected bundle, it must have %s base domain", constants.ClusterDomain)
+	}
 	return &bundleInfo, nil
 }
 
@@ -71,12 +79,6 @@ func (repo *Repository) Use(bundleName string) (*CrcBundleInfo, error) {
 	if err != nil {
 		return nil, err
 	}
-	if fmt.Sprintf(".%s", bundleInfo.ClusterInfo.AppsDomain) != constants.AppsDomain {
-		return nil, fmt.Errorf("unexpected bundle, it must have %s apps domain", constants.AppsDomain)
-	}
-	if bundleInfo.GetAPIHostname() != fmt.Sprintf("api%s", constants.ClusterDomain) {
-		return nil, fmt.Errorf("unexpected bundle, it must have %s base domain", constants.ClusterDomain)
-	}
 	if err := bundleInfo.createSymlinkOrCopyOpenShiftClient(repo.OcBinDir); err != nil {
 		return nil, err
 	}
@@ -126,7 +128,11 @@ var defaultRepo = &Repository{
 	OcBinDir: constants.CrcOcBinDir,
 }
 
-func GetCachedBundleInfo(bundleName string) (*CrcBundleInfo, error) {
+func Get(bundleName string) (*CrcBundleInfo, error) {
+	return defaultRepo.Get(bundleName)
+}
+
+func Use(bundleName string) (*CrcBundleInfo, error) {
 	return defaultRepo.Use(bundleName)
 }
 
@@ -134,5 +140,5 @@ func Extract(path string) (*CrcBundleInfo, error) {
 	if err := defaultRepo.Extract(path); err != nil {
 		return nil, err
 	}
-	return defaultRepo.Use(filepath.Base(path))
+	return defaultRepo.Get(filepath.Base(path))
 }
diff --git a/pkg/crc/machine/console.go b/pkg/crc/machine/console.go
index 6e9fb3078..c5c1a5ff0 100644
--- a/pkg/crc/machine/console.go
+++ b/pkg/crc/machine/console.go
@@ -19,12 +19,12 @@ func (client *client) GetConsoleURL() (*ConsoleResult, error) {
 		return nil, errors.Wrap(err, "Error getting the state for host")
 	}
 
-	_, crcBundleMetadata, err := getBundleMetadataFromDriver(host.Driver)
+	crcBundleMetadata, err := getBundleMetadataFromDriver(host.Driver)
 	if err != nil {
 		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/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/hyperkit/driver_darwin.go b/pkg/crc/machine/hyperkit/driver_darwin.go
index 490f09cb5..f48cde9e5 100644
--- a/pkg/crc/machine/hyperkit/driver_darwin.go
+++ b/pkg/crc/machine/hyperkit/driver_darwin.go
@@ -18,7 +18,7 @@ func CreateHost(machineConfig config.MachineConfig) *hyperkit.Driver {
 	hyperkitDriver.Cmdline = machineConfig.KernelCmdLine
 	hyperkitDriver.VmlinuzPath = machineConfig.Kernel
 	hyperkitDriver.InitrdPath = machineConfig.Initramfs
-	hyperkitDriver.HyperKitPath = filepath.Join(constants.CrcBinDir, "hyperkit")
+	hyperkitDriver.HyperKitPath = filepath.Join(constants.CrcBinDir, HyperKitCommand)
 
 	hyperkitDriver.VMNet = machineConfig.NetworkMode == network.DefaultMode
 
diff --git a/pkg/crc/machine/machine.go b/pkg/crc/machine/machine.go
index 18fd4b06e..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
@@ -29,24 +32,24 @@ func getClusterConfig(bundleInfo *bundle.CrcBundleInfo) (*ClusterConfig, error)
 		ClusterCACert: base64.StdEncoding.EncodeToString(clusterCACert),
 		KubeConfig:    bundleInfo.GetKubeConfigPath(),
 		KubeAdminPass: kubeadminPassword,
-		WebConsoleURL: constants.DefaultWebConsoleURL,
-		ClusterAPI:    constants.DefaultAPIURL,
+		WebConsoleURL: fmt.Sprintf("https://%s", bundleInfo.GetAppHostname("console-openshift-console")),
+		ClusterAPI:    fmt.Sprintf("https://%s:6443", bundleInfo.GetAPIHostname()),
 		ProxyConfig:   proxyConfig,
 	}, nil
 }
 
-func getBundleMetadataFromDriver(driver drivers.Driver) (string, *bundle.CrcBundleInfo, error) {
+func getBundleMetadataFromDriver(driver drivers.Driver) (*bundle.CrcBundleInfo, error) {
 	bundleName, err := driver.GetBundleName()
 	if err != nil {
 		err := fmt.Errorf("Error getting bundle name from CodeReady Containers instance, make sure you ran 'crc setup' and are using the latest bundle")
-		return "", nil, err
+		return nil, err
 	}
-	metadata, err := bundle.GetCachedBundleInfo(bundleName)
+	metadata, err := bundle.Get(bundleName)
 	if err != nil {
-		return "", nil, err
+		return nil, err
 	}
 
-	return bundleName, metadata, err
+	return metadata, err
 }
 
 func createLibMachineClient() (*libmachine.Client, func()) {
diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go
index 5d5799207..84e54b0bc 100644
--- a/pkg/crc/machine/start.go
+++ b/pkg/crc/machine/start.go
@@ -38,14 +38,17 @@ const minimumMemoryForMonitoring = 14336
 
 func getCrcBundleInfo(bundlePath string) (*bundle.CrcBundleInfo, error) {
 	bundleName := filepath.Base(bundlePath)
-	bundleInfo, err := bundle.GetCachedBundleInfo(bundleName)
+	bundleInfo, err := bundle.Use(bundleName)
 	if err == nil {
 		logging.Infof("Loading bundle: %s ...", bundleName)
 		return bundleInfo, nil
 	}
 	logging.Debugf("Failed to load bundle %s: %v", bundleName, err)
 	logging.Infof("Extracting bundle: %s ...", bundleName)
-	return bundle.Extract(bundlePath)
+	if _, err := bundle.Extract(bundlePath); err != nil {
+		return nil, err
+	}
+	return bundle.Use(bundleName)
 }
 
 func (client *client) updateVMConfig(startConfig StartConfig, api libmachine.API, host *host.Host) error {
@@ -105,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")
@@ -128,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")
@@ -149,7 +158,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 		machineConfig.Initramfs = crcBundleMetadata.GetInitramfsPath()
 		machineConfig.Kernel = crcBundleMetadata.GetKernelPath()
 
-		host, err = createHost(libMachineAPIClient, machineConfig)
+		host, err = createHost(ctx, libMachineAPIClient, machineConfig)
 		if err != nil {
 			return nil, errors.Wrap(err, "Error creating machine")
 		}
@@ -159,11 +168,11 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 			return nil, errors.Wrap(err, "Error loading machine")
 		}
 
-		var bundleName string
-		bundleName, crcBundleMetadata, err = getBundleMetadataFromDriver(host.Driver)
+		crcBundleMetadata, err = getBundleMetadataFromDriver(host.Driver)
 		if err != nil {
 			return nil, errors.Wrap(err, "Error loading bundle metadata")
 		}
+		bundleName := crcBundleMetadata.GetBundleName()
 		if bundleName != filepath.Base(startConfig.BundlePath) {
 			logging.Debugf("Bundle '%s' was requested, but the existing VM is using '%s'",
 				filepath.Base(startConfig.BundlePath), bundleName)
@@ -177,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")
 			}
@@ -190,6 +199,10 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 			}, nil
 		}
 
+		if _, err := bundle.Use(bundleName); err != nil {
+			return nil, err
+		}
+
 		if client.useVSock() {
 			if err := exposePorts(); err != nil {
 				return nil, err
@@ -215,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")
 	}
@@ -240,16 +253,19 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 	defer sshRunner.Close()
 
 	logging.Debug("Waiting until ssh is available")
-	if err := cluster.WaitForSSH(sshRunner); err != nil {
+	if err := cluster.WaitForSSH(ctx, sshRunner); err != nil {
 		return nil, errors.Wrap(err, "Failed to connect to the CRC VM with SSH -- host might be unreachable")
 	}
 	logging.Info("CodeReady Containers VM is running")
 
 	// Post VM start immediately update SSH key and copy kubeconfig to instance
 	// dir and VM
-	if err := updateSSHKeyAndCopyKubeconfig(sshRunner, client.name, crcBundleMetadata); err != nil {
+	if err := updateSSHKeyPair(sshRunner); err != nil {
 		return nil, errors.Wrap(err, "Error updating public key")
 	}
+	if err := copyKubeconfig(client.name, crcBundleMetadata); err != nil {
+		return nil, errors.Wrap(err, "Error copying kubeconfig file")
+	}
 
 	// Trigger disk resize, this will be a no-op if no disk size change is needed
 	if _, _, err = sshRunner.Run("sudo xfs_growfs / >/dev/null"); err != nil {
@@ -344,7 +360,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 		return nil, errors.Wrap(err, "Failed to renew TLS certificates: please check if a newer CodeReady Containers release is available")
 	}
 
-	if err := cluster.WaitForAPIServer(ocConfig); err != nil {
+	if err := cluster.WaitForAPIServer(ctx, ocConfig); err != nil {
 		return nil, errors.Wrap(err, "Error waiting for apiserver")
 	}
 
@@ -356,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")
 	}
@@ -392,18 +412,17 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 
 	logging.Info("Starting OpenShift cluster ... [waiting for the cluster to stabilize]")
 
-	if err := cluster.WaitForClusterStable(ocConfig, client.monitoringEnabled()); err != nil {
+	if err := cluster.WaitForClusterStable(ctx, ocConfig, client.monitoringEnabled()); err != nil {
 		logging.Errorf("Cluster is not ready: %v", err)
 	}
 
-	waitForProxyPropagation(ocConfig, proxyConfig)
+	waitForProxyPropagation(ctx, ocConfig, proxyConfig)
 
 	logging.Info("Adding crc-admin and crc-developer contexts to kubeconfig...")
 	if err := writeKubeconfig(instanceIP, clusterConfig); err != nil {
-		logging.Warnf("Cannot update kubeconfig: %v", err)
+		logging.Errorf("Cannot update kubeconfig: %v", err)
 	}
 
-	logging.Warn("The cluster might report a degraded or error state. This is expected since several operators have been disabled to lower the resource usage. For more information, please consult the documentation")
 	return &StartResult{
 		KubeletStarted: true,
 		ClusterConfig:  *clusterConfig,
@@ -458,12 +477,12 @@ func makeDaemonVisibleToHyperkit(name string) error {
 	return nil
 }
 
-func createHost(api libmachine.API, machineConfig config.MachineConfig) (*host.Host, error) {
+func createHost(ctx context.Context, api libmachine.API, machineConfig config.MachineConfig) (*host.Host, error) {
 	vm, err := newHost(api, machineConfig)
 	if err != nil {
 		return nil, fmt.Errorf("Error creating new host: %s", err)
 	}
-	if err := api.Create(vm); err != nil {
+	if err := api.Create(ctx, vm); err != nil {
 		return nil, fmt.Errorf("Error creating the VM: %s", err)
 	}
 	return vm, nil
@@ -516,11 +535,7 @@ func updateSSHKeyPair(sshRunner *crcssh.Runner) error {
 	return err
 }
 
-func updateSSHKeyAndCopyKubeconfig(sshRunner *crcssh.Runner, name string, crcBundleMetadata *bundle.CrcBundleInfo) error {
-	if err := updateSSHKeyPair(sshRunner); err != nil {
-		return fmt.Errorf("Error updating SSH Keys: %v", err)
-	}
-
+func copyKubeconfig(name string, crcBundleMetadata *bundle.CrcBundleInfo) error {
 	kubeConfigFilePath := filepath.Join(constants.MachineInstanceDir, name, "kubeconfig")
 	if _, err := os.Stat(kubeConfigFilePath); err == nil {
 		return nil
@@ -554,7 +569,7 @@ func ensureProxyIsConfiguredInOpenShift(ocConfig oc.Config, sshRunner *crcssh.Ru
 	return cluster.AddProxyConfigToCluster(sshRunner, ocConfig, proxy)
 }
 
-func waitForProxyPropagation(ocConfig oc.Config, proxyConfig *network.ProxyConfig) {
+func waitForProxyPropagation(ctx context.Context, ocConfig oc.Config, proxyConfig *network.ProxyConfig) {
 	if !proxyConfig.IsEnabled() {
 		return
 	}
@@ -572,7 +587,7 @@ func waitForProxyPropagation(ocConfig oc.Config, proxyConfig *network.ProxyConfi
 		return nil
 	}
 
-	if err := crcerrors.RetryAfter(300*time.Second, checkProxySettingsForOperator, 2*time.Second); err != nil {
+	if err := crcerrors.RetryAfterWithContext(ctx, 300*time.Second, checkProxySettingsForOperator, 2*time.Second); err != nil {
 		logging.Debug("Failed to propagate proxy settings to cluster")
 	}
 }
diff --git a/pkg/crc/machine/status.go b/pkg/crc/machine/status.go
index cd06b6a73..43630ae04 100644
--- a/pkg/crc/machine/status.go
+++ b/pkg/crc/machine/status.go
@@ -28,23 +28,24 @@ func (client *client) Status() (*ClusterStatusResult, error) {
 		return nil, errors.Wrap(err, "Cannot get machine state")
 	}
 
+	crcBundleMetadata, err := getBundleMetadataFromDriver(host.Driver)
+	if err != nil {
+		return nil, errors.Wrap(err, "Error loading bundle metadata")
+	}
+
 	if vmStatus != state.Running {
 		return &ClusterStatusResult{
-			CrcStatus:       vmStatus,
-			OpenshiftStatus: "Stopped",
+			CrcStatus:        vmStatus,
+			OpenshiftStatus:  "Stopped",
+			OpenshiftVersion: crcBundleMetadata.GetOpenshiftVersion(),
 		}, nil
 	}
 
-	_, crcBundleMetadata, err := getBundleMetadataFromDriver(host.Driver)
-	if err != nil {
-		return nil, errors.Wrap(err, "Error loading bundle metadata")
-	}
-
 	ip, err := getIP(host, client.useVSock())
 	if err != nil {
 		return nil, errors.Wrap(err, "Error getting ip")
 	}
-	sshRunner, err := crcssh.CreateRunner(ip, getSSHPort(client.useVSock()), constants.GetPrivateKeyPath(), constants.GetRsaPrivateKeyPath())
+	sshRunner, err := crcssh.CreateRunner(ip, getSSHPort(client.useVSock()), constants.GetPrivateKeyPath(), constants.GetRsaPrivateKeyPath(), crcBundleMetadata.GetSSHKeyPath())
 	if err != nil {
 		return nil, errors.Wrap(err, "Error creating the ssh client")
 	}
@@ -52,7 +53,7 @@ func (client *client) Status() (*ClusterStatusResult, error) {
 	// check if all the clusteroperators are running
 	diskSize, diskUse, err := cluster.GetRootPartitionUsage(sshRunner)
 	if err != nil {
-		return nil, errors.Wrap(err, "Cannot get root partition usage")
+		logging.Debugf("Cannot get root partition usage: %v", err)
 	}
 	return &ClusterStatusResult{
 		CrcStatus:        state.Running,
diff --git a/pkg/crc/machine/stop.go b/pkg/crc/machine/stop.go
index 3b38f20ce..f9a4a4921 100644
--- a/pkg/crc/machine/stop.go
+++ b/pkg/crc/machine/stop.go
@@ -17,9 +17,9 @@ func (client *client) Stop() (state.State, error) {
 
 	logging.Info("Stopping the OpenShift cluster, this may take a few minutes...")
 	if err := host.Stop(); err != nil {
-		status, err := host.Driver.GetState()
-		if err != nil {
-			logging.Debugf("Cannot get VM status after stopping it: %v", err)
+		status, stateErr := host.Driver.GetState()
+		if stateErr != nil {
+			logging.Debugf("Cannot get VM status after stopping it: %v", stateErr)
 		}
 		return status, errors.Wrap(err, "Cannot stop machine")
 	}
diff --git a/pkg/crc/network/utils.go b/pkg/crc/network/utils.go
index 04923aae6..638f630f3 100644
--- a/pkg/crc/network/utils.go
+++ b/pkg/crc/network/utils.go
@@ -50,8 +50,8 @@ func CheckCRCLocalDNSReachableFromHost(bundle *bundle.CrcBundleInfo, expectedIP
 		appsHostname := bundle.GetAppHostname("foo")
 		ip, err = net.LookupIP(appsHostname)
 		if err != nil {
-			// Right now goodhosts fallback is not implemented in windows so
-			// this checks should still return the error.
+			// Right now admin helper fallback is not implemented on windows so
+			// this check should still return an error.
 			if runtime.GOOS == "windows" {
 				return err
 			}
diff --git a/pkg/crc/preflight/preflight_checks_common.go b/pkg/crc/preflight/preflight_checks_common.go
index 6d7663c85..235d7773d 100644
--- a/pkg/crc/preflight/preflight_checks_common.go
+++ b/pkg/crc/preflight/preflight_checks_common.go
@@ -73,7 +73,7 @@ func checkBundleExtracted() error {
 		return nil
 	}
 	logging.Infof("Checking if %s exists", constants.DefaultBundlePath)
-	if _, err := bundle.GetCachedBundleInfo(constants.GetDefaultBundle()); err != nil {
+	if _, err := bundle.Get(constants.GetDefaultBundle()); err != nil {
 		logging.Debugf("error getting bundle info for %s: %v", constants.GetDefaultBundle(), err)
 		return err
 	}
@@ -106,7 +106,7 @@ func fixBundleExtracted() error {
 		}
 	}
 
-	_, err := bundle.GetCachedBundleInfo(constants.GetDefaultBundle())
+	_, err := bundle.Get(constants.GetDefaultBundle())
 	if err != nil {
 		logging.Infof("Uncompressing %s", constants.GetDefaultBundle())
 		_, err := bundle.Extract(constants.DefaultBundlePath)
diff --git a/pkg/crc/ssh/client.go b/pkg/crc/ssh/client.go
index 28e6a0930..bbb32fd87 100644
--- a/pkg/crc/ssh/client.go
+++ b/pkg/crc/ssh/client.go
@@ -67,7 +67,7 @@ func clientConfig(user string, keys []string) (*ssh.ClientConfig, error) {
 		Auth: []ssh.AuthMethod{ssh.PublicKeys(privateKeys...)},
 		// #nosec G106
 		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
-		Timeout:         time.Minute,
+		Timeout:         10 * time.Second,
 	}, nil
 }
 
diff --git a/pkg/crc/ssh/ssh.go b/pkg/crc/ssh/ssh.go
index 4badb8366..5d5a4c81c 100644
--- a/pkg/crc/ssh/ssh.go
+++ b/pkg/crc/ssh/ssh.go
@@ -67,7 +67,7 @@ func (runner *Runner) CopyFile(srcFilename string, destFilename string, mode os.
 
 func (runner *Runner) runSSHCommand(command string, runPrivate bool) (string, string, error) {
 	if runPrivate {
-		logging.Debugf("Running private SSH command")
+		logging.Debugf("Running SSH command: <hidden>")
 	} else {
 		logging.Debugf("Running SSH command: %s", command)
 	}
diff --git a/pkg/crc/validation/validation.go b/pkg/crc/validation/validation.go
index dca6fab9b..8a3bb78d1 100644
--- a/pkg/crc/validation/validation.go
+++ b/pkg/crc/validation/validation.go
@@ -79,7 +79,7 @@ func ValidateBundlePath(bundle string) error {
 
 func ValidateBundle(bundlePath string) error {
 	bundleName := filepath.Base(bundlePath)
-	_, err := bundle.GetCachedBundleInfo(bundleName)
+	_, err := bundle.Get(bundleName)
 	if err != nil {
 		return ValidateBundlePath(bundlePath)
 	}
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/pkg/libmachine/libmachine.go b/pkg/libmachine/libmachine.go
index e85bd18e4..56ab0d79a 100644
--- a/pkg/libmachine/libmachine.go
+++ b/pkg/libmachine/libmachine.go
@@ -1,6 +1,7 @@
 package libmachine
 
 import (
+	"context"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -20,7 +21,7 @@ import (
 type API interface {
 	io.Closer
 	NewHost(driverName string, driverPath string, rawDriver []byte) (*host.Host, error)
-	Create(h *host.Host) error
+	Create(ctx context.Context, h *host.Host) error
 	persist.Store
 }
 
@@ -86,7 +87,7 @@ func (api *Client) Load(name string) (*host.Host, error) {
 
 // Create is the wrapper method which covers all of the boilerplate around
 // actually creating, provisioning, and persisting an instance in the store.
-func (api *Client) Create(h *host.Host) error {
+func (api *Client) Create(ctx context.Context, h *host.Host) error {
 	log.Debug("Running pre-create checks...")
 
 	if err := h.Driver.PreCreateCheck(); err != nil {
@@ -99,7 +100,7 @@ func (api *Client) Create(h *host.Host) error {
 
 	log.Debug("Creating machine...")
 
-	if err := api.performCreate(h); err != nil {
+	if err := api.performCreate(ctx, h); err != nil {
 		return fmt.Errorf("Error creating machine: %s", err)
 	}
 
@@ -111,7 +112,7 @@ func (api *Client) Create(h *host.Host) error {
 	return nil
 }
 
-func (api *Client) performCreate(h *host.Host) error {
+func (api *Client) performCreate(ctx context.Context, h *host.Host) error {
 	if err := h.Driver.Create(); err != nil {
 		return fmt.Errorf("Error in driver during machine creation: %s", err)
 	}
@@ -121,7 +122,7 @@ func (api *Client) performCreate(h *host.Host) error {
 	}
 
 	log.Debug("Waiting for machine to be running, this may take a few minutes...")
-	if err := crcerrors.RetryAfter(3*time.Minute, host.MachineInState(h.Driver, state.Running), 3*time.Second); err != nil {
+	if err := crcerrors.RetryAfterWithContext(ctx, 3*time.Minute, host.MachineInState(h.Driver, state.Running), 3*time.Second); err != nil {
 		return fmt.Errorf("Error waiting for machine to be running: %s", err)
 	}
 
diff --git a/pkg/os/exec.go b/pkg/os/exec.go
index 46293e981..b878d0fa9 100644
--- a/pkg/os/exec.go
+++ b/pkg/os/exec.go
@@ -36,7 +36,7 @@ func run(command string, args []string, env map[string]string) (string, string,
 }
 
 func runPrivate(command string, args []string, env map[string]string) (string, string, error) {
-	logging.Debugf("About to run a hidden command")
+	logging.Debugf("Running '%s <hidden arguments>'", command)
 	return runCmd(command, args, env)
 }
 
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/cmd/crc/cmd/start_test.go b/cmd/crc/cmd/start_test.go
index 52e92ccaa..38e261bc0 100644
--- a/cmd/crc/cmd/start_test.go
+++ b/cmd/crc/cmd/start_test.go
@@ -6,19 +6,23 @@ import (
 	"runtime"
 	"testing"
 
-	"github.com/code-ready/crc/pkg/crc/constants"
 	crcErrors "github.com/code-ready/crc/pkg/crc/errors"
 	"github.com/code-ready/crc/pkg/os/shell"
 	"github.com/stretchr/testify/assert"
 )
 
+const (
+	defaultWebConsoleURL = "https://console-openshift-console.apps-crc.testing"
+	defaultAPIURL        = "https://api.crc.testing:6443"
+)
+
 func TestRenderActionPlainSuccess(t *testing.T) {
 	out := new(bytes.Buffer)
 	assert.NoError(t, render(&startResult{
 		Success: true,
 		ClusterConfig: &clusterConfig{
-			URL:           constants.DefaultAPIURL,
-			WebConsoleURL: constants.DefaultWebConsoleURL,
+			WebConsoleURL: defaultWebConsoleURL,
+			URL:           defaultAPIURL,
 			AdminCredentials: credentials{
 				Username: "kubeadmin",
 				Password: "secret",
@@ -30,7 +34,15 @@ func TestRenderActionPlainSuccess(t *testing.T) {
 		},
 	}, out, ""))
 
+	var userShell string
 	userShell, err := shell.GetShell("")
+	if err != nil {
+		if runtime.GOOS == "windows" {
+			userShell, err = shell.GetShell("cmd")
+		} else {
+			userShell, err = shell.GetShell("bash")
+		}
+	}
 	assert.NoError(t, err)
 	assert.Equal(t, expectedTemplate(userShell), out.String())
 }
@@ -50,8 +62,8 @@ func TestRenderActionJSONSuccess(t *testing.T) {
 	assert.NoError(t, render(&startResult{
 		Success: true,
 		ClusterConfig: &clusterConfig{
-			WebConsoleURL: constants.DefaultWebConsoleURL,
-			URL:           constants.DefaultAPIURL,
+			WebConsoleURL: defaultWebConsoleURL,
+			URL:           defaultAPIURL,
 			AdminCredentials: credentials{
 				Username: "kubeadmin",
 				Password: "secret",
diff --git a/pkg/crc/api/api_test.go b/pkg/crc/api/api_test.go
index e97f8119a..76822ad87 100644
--- a/pkg/crc/api/api_test.go
+++ b/pkg/crc/api/api_test.go
@@ -31,7 +31,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 +118,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
@@ -218,7 +225,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 {
@@ -247,3 +254,10 @@ func (s *skipPreflights) Set(key string, value interface{}) error {
 func (s *skipPreflights) Unset(key string) error {
 	return s.storage.Unset(key)
 }
+
+type mockLogger struct {
+}
+
+func (*mockLogger) Messages() []string {
+	return []string{"message 1", "message 2", "message 3"}
+}
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/os/util_test.go b/pkg/os/util_test.go
index 5dd42e050..7a69f4845 100644
--- a/pkg/os/util_test.go
+++ b/pkg/os/util_test.go
@@ -7,6 +7,8 @@ import (
 	"runtime"
 	"testing"
 
+	"os/user"
+
 	"github.com/stretchr/testify/assert"
 )
 
@@ -82,7 +84,13 @@ func TestFileExists(t *testing.T) {
 	if runtime.GOOS == "windows" {
 		assert.True(t, FileExists(filename))
 	} else {
-		assert.False(t, FileExists(filename))
+		user, _ := user.Current()
+		if user != nil && user.Uid != "0" {
+			assert.False(t, FileExists(filename))
+		} else {
+			/* If the user is root, chmod 000 $dir won't block file existence checks */
+			assert.True(t, FileExists(filename))
+		}
 	}
 
 	filename = filepath.Join(dirname, "nonexistent")
diff --git a/test/e2e/crcsuite/crcsuite.go b/test/e2e/crcsuite/crcsuite.go
index 1e13e030c..7214a346b 100644
--- a/test/e2e/crcsuite/crcsuite.go
+++ b/test/e2e/crcsuite/crcsuite.go
@@ -3,7 +3,6 @@ package crcsuite
 import (
 	"crypto/tls"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"os/user"
@@ -219,9 +218,9 @@ func CheckOutputMatchWithRetry(retryCount int, retryTime string, command string,
 // CheckCRCStatus checks that output of status command
 // matches given regex
 func CheckCRCStatus(state string) error {
-	expression := `.*Running \(v\d+\.\d+\.\d+.*\).*`
+	expression := `.*OpenShift: .*Running \(v\d+\.\d+\.\d+.*\).*`
 	if state == "stopped" {
-		expression = ".*Stopped.*"
+		expression = ".*OpenShift: .*Stopped.*"
 	}
 
 	err := clicumber.ExecuteCommand("crc status --log-level debug")
@@ -310,20 +309,8 @@ func ConfigFileInCRCHomeContainsKey(format string, configFile string, condition
 }
 
 func LoginToOcClusterSucceedsOrFails(expected string) error {
-
-	bundle := strings.Split(bundleName, ".crcbundle")[0]
-	pswdLocation := filepath.Join(CRCHome, "cache", bundle, "kubeadmin-password")
-
-	pswd, err := ioutil.ReadFile(pswdLocation)
-
-	if err != nil {
-		return err
-	}
-
-	cmd := fmt.Sprintf("oc login --insecure-skip-tls-verify -u kubeadmin -p %s https://api.crc.testing:6443", pswd)
-	err = clicumber.ExecuteCommandSucceedsOrFails(cmd, expected)
-
-	return err
+	cmd := "oc config use-context crc-admin"
+	return clicumber.ExecuteCommandSucceedsOrFails(cmd, expected)
 }
 
 func SetKubeConfigContextSucceedsOrFails(context, expected string) error {