Repository: kubernetes/minikube (go)

Issue:
Upon start, minikube splashes PROXY variables with password on the screen
### What Happened?

When minikube start, it displays env variables like http_proxy and https_proxy which can contain passwords. It would be nice if the password was masked with asterisks or something like that.

### Attach the log file

No relevant logs. Just the default output of minikube upon start.

### Operating System

Ubuntu

### Driver

Docker

Pull request description:
Masking http(s)_proxy password from startup output.
fixes #17115 

Patch:
diff --git a/pkg/minikube/node/config.go b/pkg/minikube/node/config.go
index f58839aa5..e7f271a65 100644
--- a/pkg/minikube/node/config.go
+++ b/pkg/minikube/node/config.go
@@ -21,7 +21,9 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"regexp"
 	"strconv"
+	"strings"
 	"sync"
 
 	"github.com/spf13/viper"
@@ -39,11 +41,43 @@ import (
 	"k8s.io/minikube/pkg/util/lock"
 )
 
+func maskProxyPassword(v string) string {
+	parts := strings.Split(v, "=")
+	// Is it an attribution variable?
+	if len(parts) == 2 {
+		key := strings.ToUpper(parts[0])
+		// Is it a proxy setting?
+		if key == "HTTP_PROXY" || key == "HTTPS_PROXY" {
+			proxyValue := parts[1]
+			// Proxy variable values SHOULD have a value like
+			// https(s)://<whatever>
+			proxyAddressParts := strings.Split(proxyValue, "://")
+			if len(proxyAddressParts) == 2 {
+				proxyURL := ""
+				proxyURL = proxyAddressParts[1]
+				// Let's store the username, the URL and and optional port address
+				pattern := `([^:]+):.+(@[\w\.]+)(:\d+)?`
+				regexpPattern := regexp.MustCompile(pattern)
+				matches := regexpPattern.FindStringSubmatch(proxyURL)
+				mask := "*****"
+				if len(matches) == 4 {
+					proxyValue = fmt.Sprintf("%s://%s:%s%s%s", proxyAddressParts[0], matches[1], mask, matches[2], matches[3])
+				} else if len(matches) == 3 {
+					proxyValue = fmt.Sprintf("%s//%s:%s@%s", proxyAddressParts[0], matches[1], mask, matches[2])
+				}
+			}
+			v = key + "=" + proxyValue
+		}
+	}
+	return v
+}
+
 func showVersionInfo(k8sVersion string, cr cruntime.Manager) {
 	version, _ := cr.Version()
 	register.Reg.SetStep(register.PreparingKubernetes)
 	out.Step(cr.Style(), "Preparing Kubernetes {{.k8sVersion}} on {{.runtime}} {{.runtimeVersion}} ...", out.V{"k8sVersion": k8sVersion, "runtime": cr.Name(), "runtimeVersion": version})
 	for _, v := range config.DockerOpt {
+		v = maskProxyPassword(v)
 		out.Infof("opt {{.docker_option}}", out.V{"docker_option": v})
 	}
 	for _, v := range config.DockerEnv {
diff --git a/pkg/minikube/node/start.go b/pkg/minikube/node/start.go
old mode 100644
new mode 100755
index fd4c00921..b8be4fc9e
--- a/pkg/minikube/node/start.go
+++ b/pkg/minikube/node/start.go
@@ -723,9 +723,15 @@ func validateNetwork(h *host.Host, r command.Runner, imageRepository string) (st
 				out.Styled(style.Internet, "Found network options:")
 				optSeen = true
 			}
+			k = strings.ToUpper(k) // let's get the key right away to mask password from output
+			// If http(s)_proxy contains password, let's not splatter on the screen
+			if k == "HTTP_PROXY" || k == "HTTPS_PROXY" {
+				pattern := `//(\w+):\w+@`
+				regexpPattern := regexp.MustCompile(pattern)
+				v = regexpPattern.ReplaceAllString(v, "//$1:*****@")
+			}
 			out.Infof("{{.key}}={{.value}}", out.V{"key": k, "value": v})
 			ipExcluded := proxy.IsIPExcluded(ip) // Skip warning if minikube ip is already in NO_PROXY
-			k = strings.ToUpper(k)               // for http_proxy & https_proxy
 			if (k == "HTTP_PROXY" || k == "HTTPS_PROXY") && !ipExcluded && !warnedOnce {
 				out.WarningT("You appear to be using a proxy, but your NO_PROXY environment does not include the minikube IP ({{.ip_address}}).", out.V{"ip_address": ip})
 				out.Styled(style.Documentation, "Please see {{.documentation_url}} for more details", out.V{"documentation_url": "https://minikube.sigs.k8s.io/docs/handbook/vpn_and_proxy/"})


Test patch:
diff --git a/pkg/minikube/node/config_test.go b/pkg/minikube/node/config_test.go
new file mode 100644
index 000000000..c40df55b5
--- /dev/null
+++ b/pkg/minikube/node/config_test.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2016 The Kubernetes Authors All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package node
+
+import (
+	"testing"
+)
+
+func Test_maskProxyPassword(t *testing.T) {
+	type dockerOptTest struct {
+		input  string
+		output string
+	}
+	var tests = []dockerOptTest{
+		{
+			input:  "cats",
+			output: "cats",
+		},
+		{
+			input:  "myDockerOption=value",
+			output: "myDockerOption=value",
+		},
+		{
+			input:  "http_proxy=http://minikube.sigs.k8s.io",
+			output: "HTTP_PROXY=http://minikube.sigs.k8s.io",
+		},
+		{
+			input:  "https_proxy=http://jdoe@minikube.sigs.k8s.io:8080",
+			output: "HTTPS_PROXY=http://jdoe@minikube.sigs.k8s.io:8080",
+		},
+		{
+			input:  "https_proxy=https://mary:iam$Fake!password@minikube.sigs.k8s.io:8080",
+			output: "HTTPS_PROXY=https://mary:*****@minikube.sigs.k8s.io:8080",
+		},
+		{
+			input:  "http_proxy=http://jdoe:%n0tRe@al:Password!@minikube.sigs.k8s.io:8080",
+			output: "HTTP_PROXY=http://jdoe:*****@minikube.sigs.k8s.io:8080",
+		},
+		{
+			input:  "http_proxy=http://jo@han:n0tRe@al:&Password!@minikube.sigs.k8s.io:8080",
+			output: "HTTP_PROXY=http://jo@han:*****@minikube.sigs.k8s.io:8080",
+		},
+		{
+			input:  "http_proxy=http://k@r3n!:an0th3erF@akeP@55word@minikube.sigs.k8s.io",
+			output: "HTTP_PROXY=http://k@r3n!:*****@minikube.sigs.k8s.io",
+		},
+		{
+			input:  "https_proxy=https://fr@ank5t3in:an0th3erF@akeP@55word@minikube.sigs.k8s.io",
+			output: "HTTPS_PROXY=https://fr@ank5t3in:*****@minikube.sigs.k8s.io",
+		},
+	}
+	for _, test := range tests {
+		got := maskProxyPassword(test.input)
+		if got != test.output {
+			t.Errorf("maskProxyPassword(\"%v\"): got %v, expected %v", test.input, got, test.output)
+		}
+	}
+}