Repository: code-ready/crc
Pull request #2072 (2021-03-08 12:41:21)

Update kubeadmin password for each fresh start
This patch will create a random password and then update the cluster
secret for htpasswd. It also update the cache kubeadmin-password file
so that all the commands which rely on it doesn't break.

fixes: #2048


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

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

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

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

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

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

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci[bot]: @praveenkumar: The following test **failed**, say `/retest` to rerun all failed tests:

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

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

<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 -->
praveenkumar: I am thinking why we have any password set from snc side? We do have kubecofig file for `system:admin` role and same is consumed by all the `oc` commands. We can consume same kubeconfig file for creating <user> - <password> from crc side?
guillaumerose: Yes we can remove the kubeadmin file from the bundle :+1: 
openshift-ci[bot]: @praveenkumar: The following test **failed**, say `/retest` to rerun all failed tests:

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

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

<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 937511936..9cbad1733 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -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 f3eacacff..c08272988 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 SHELL := /bin/bash
 
-BUNDLE_VERSION = 4.7.1
+BUNDLE_VERSION = 4.7.2
 BUNDLE_EXTENSION = crcbundle
 CRC_VERSION = 1.23.1
 COMMIT_SHA=$(shell git rev-parse --short HEAD)
@@ -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 316897b4d..e5682be2f 100644
--- a/centos_ci.sh
+++ b/centos_ci.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 # bundle location
-BUNDLE_VERSION=4.7.1
+BUNDLE_VERSION=4.7.2
 BUNDLE=crc_libvirt_$BUNDLE_VERSION.crcbundle
 GO_VERSION=1.14.13
 
diff --git a/cmd/crc/cmd/config/config.go b/cmd/crc/cmd/config/config.go
index 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/go.mod b/go.mod
index 26cbd0f61..7b12e73c0 100644
--- a/go.mod
+++ b/go.mod
@@ -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..534a6fef9 100644
--- a/go.sum
+++ b/go.sum
@@ -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/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/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/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/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 d55efab0c..e4f874178 100644
--- a/pkg/crc/machine/bundle/metadata.go
+++ b/pkg/crc/machine/bundle/metadata.go
@@ -111,13 +111,27 @@ func (bundle *CrcBundleInfo) GetInitramfsPath() string {
 }
 
 func (bundle *CrcBundleInfo) GetKubeadminPassword() (string, error) {
-	rawData, err := ioutil.ReadFile(bundle.resolvePath(bundle.ClusterInfo.KubeadminPasswordFile))
+	// First check if ~/.crc/machine/crc/kubeadmin-password file exist and read password from there
+	kubeAdminPasswordFile := filepath.Join(constants.MachineInstanceDir, constants.DefaultName, bundle.ClusterInfo.KubeadminPasswordFile)
+	if _, err := os.Stat(kubeAdminPasswordFile); err != nil {
+		kubeAdminPasswordFile = bundle.resolvePath(bundle.ClusterInfo.KubeadminPasswordFile)
+	}
+	rawData, err := ioutil.ReadFile(kubeAdminPasswordFile)
 	if err != nil {
 		return "", err
 	}
 	return strings.TrimSpace(string(rawData)), nil
 }
 
+func (bundle *CrcBundleInfo) UpdateKubeadminPassword(kubeadminPassword string) error {
+	kubeAdminPasswordFile := filepath.Join(constants.MachineInstanceDir, constants.DefaultName, bundle.ClusterInfo.KubeadminPasswordFile)
+	err := ioutil.WriteFile(kubeAdminPasswordFile, []byte(kubeadminPassword), 0600)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 func (bundle *CrcBundleInfo) GetBundleBuildTime() (time.Time, error) {
 	return time.Parse(time.RFC3339, strings.TrimSpace(bundle.BuildInfo.BuildTime))
 }
diff --git a/pkg/crc/machine/console.go b/pkg/crc/machine/console.go
index 630496204..c5c1a5ff0 100644
--- a/pkg/crc/machine/console.go
+++ b/pkg/crc/machine/console.go
@@ -24,7 +24,7 @@ func (client *client) GetConsoleURL() (*ConsoleResult, error) {
 		return nil, errors.Wrap(err, "Error loading bundle metadata")
 	}
 
-	clusterConfig, err := getClusterConfig(crcBundleMetadata)
+	clusterConfig, err := getClusterConfig(crcBundleMetadata, "")
 	if err != nil {
 		return nil, errors.Wrap(err, "Error loading cluster configuration")
 	}
diff --git a/pkg/crc/machine/machine.go b/pkg/crc/machine/machine.go
index ea0e12050..ae7fdd3df 100644
--- a/pkg/crc/machine/machine.go
+++ b/pkg/crc/machine/machine.go
@@ -12,11 +12,14 @@ import (
 	"github.com/code-ready/machine/libmachine/drivers"
 )
 
-func getClusterConfig(bundleInfo *bundle.CrcBundleInfo) (*ClusterConfig, error) {
+func getClusterConfig(bundleInfo *bundle.CrcBundleInfo, generatedKubeadminPassword string) (*ClusterConfig, error) {
 	kubeadminPassword, err := bundleInfo.GetKubeadminPassword()
 	if err != nil {
 		return nil, fmt.Errorf("Error reading kubeadmin password from bundle %v", err)
 	}
+	if generatedKubeadminPassword != "" {
+		kubeadminPassword = generatedKubeadminPassword
+	}
 	proxyConfig, err := getProxyConfig(bundleInfo.ClusterInfo.BaseDomain)
 	if err != nil {
 		return nil, err
diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go
index 191906b97..84e54b0bc 100644
--- a/pkg/crc/machine/start.go
+++ b/pkg/crc/machine/start.go
@@ -108,6 +108,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 
 	// Pre-VM start
 	var host *host.Host
+	var kubeAdminUserPassword string
 	exists, err := client.Exists()
 	if err != nil {
 		return nil, errors.Wrap(err, "Cannot determine if VM exists")
@@ -131,6 +132,11 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 			NetworkMode: client.networkMode(),
 		}
 
+		kubeAdminUserPassword, err = cluster.GenerateRandomPasswordHash(23)
+		if err != nil {
+			return nil, errors.Wrap(err, "Cannot generate the kubeadmin user password")
+		}
+
 		crcBundleMetadata, err = getCrcBundleInfo(startConfig.BundlePath)
 		if err != nil {
 			return nil, errors.Wrap(err, "Error getting bundle metadata")
@@ -152,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")
 		}
@@ -180,7 +186,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 		}
 		if vmState == state.Running {
 			logging.Infof("A CodeReady Containers VM for OpenShift %s is already running", crcBundleMetadata.GetOpenshiftVersion())
-			clusterConfig, err := getClusterConfig(crcBundleMetadata)
+			clusterConfig, err := getClusterConfig(crcBundleMetadata, kubeAdminUserPassword)
 			if err != nil {
 				return nil, errors.Wrap(err, "Cannot create cluster configuration")
 			}
@@ -222,7 +228,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 		}
 	}
 
-	clusterConfig, err := getClusterConfig(crcBundleMetadata)
+	clusterConfig, err := getClusterConfig(crcBundleMetadata, kubeAdminUserPassword)
 	if err != nil {
 		return nil, errors.Wrap(err, "Cannot create cluster configuration")
 	}
@@ -247,7 +253,7 @@ 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")
@@ -354,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")
 	}
 
@@ -366,6 +372,10 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 		return nil, errors.Wrap(err, "Failed to update cluster pull secret")
 	}
 
+	if err := cluster.UpdateKubeAdminUserPassword(kubeAdminUserPassword, ocConfig, crcBundleMetadata); err != nil {
+		return nil, errors.Wrap(err, "Failed to update kubeadmin user password")
+	}
+
 	if err := cluster.EnsureClusterIDIsNotEmpty(ocConfig); err != nil {
 		return nil, errors.Wrap(err, "Failed to update cluster ID")
 	}
@@ -402,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,
@@ -468,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
@@ -560,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
 	}
@@ -578,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/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/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/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/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..1bbab9d68 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -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 a65ff7a64..38e261bc0 100644
--- a/cmd/crc/cmd/start_test.go
+++ b/cmd/crc/cmd/start_test.go
@@ -34,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())
 }
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")