Repository: linkerd/linkerd2
Pull request #7456 (2021-12-10 14:34:23)

Fix recommended value formatting for '--set clusterNetworks'
The solution `linkerd check` suggests for setting a correct
`clusterNetworks` value does not work when there are more than
one podCIDR. Here's an example recommendation by that command:

```
× cluster networks contains all node podCIDRs
    node has podCIDR(s) [242.3.68.0/25 242.3.68.128/25 242.3.66.0/25 242.3.67.128/25 242.3.69.0/25 242.3.66.128/25 242.3.64.128/25 242.3.65.128/25 242.3.67.0/25 242.3.65.0/25 242.3.64.0/25] which are not contained in the Linkerd clusterNetworks.
	Try installing linkerd via --set clusterNetworks=242.3.68.0/25,242.3.68.128/25,242.3.66.0/25,242.3.67.128/25,242.3.69.0/25,242.3.66.128/25,242.3.64.128/25,242.3.65.128/25,242.3.67.0/25,242.3.65.0/25,242.3.64.0/25
    see https://linkerd.io/2.11/checks/#l5d-cluster-networks-cidr for hints
```

If you follow that suggestion and run

```
linkerd install --set clusterNetworks=242.3.68.0/25,242.3.68.128/25,242.3.66.0/25,242.3.67.128/25,242.3.69.0/25,242.3.66.128/25,242.3.64.128/25,242.3.65.128/25,242.3.67.0/25,242.3.65.0/25,242.3.64.0/25
```

you will get the following error:

```
Error: failed parsing --set data: key "128/25" has no value (cannot end with ,)
```

Once merged, this change will make sure the list of recommended podCIDRS
are separated with escaped comma (i.e `\,`) and enclosed with `"`.

Signed-off-by: Elvin Efendi <elvin.efendiyev@gmail.com>


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 6b2cf3f0c..3a31b984e 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -16,14 +16,14 @@ linters:
   - bodyclose
   - deadcode
   - depguard
+  - exportloopref
   - goconst
-  - golint
   - gosimple
   - govet
   - ineffassign
   - misspell
   - nakedret
-  - scopelint
+  - revive
   - typecheck
   - unconvert
   - unparam
diff --git a/cli/cmd/metrics.go b/cli/cmd/metrics.go
index a5c265ae7..e81fcce76 100644
--- a/cli/cmd/metrics.go
+++ b/cli/cmd/metrics.go
@@ -13,11 +13,13 @@ import (
 type metricsOptions struct {
 	namespace string
 	pod       string
+	obfuscate bool
 }
 
 func newMetricsOptions() *metricsOptions {
 	return &metricsOptions{
-		pod: "",
+		pod:       "",
+		obfuscate: false,
 	}
 }
 
@@ -85,11 +87,20 @@ func newCmdMetrics() *cobra.Command {
 			var buf bytes.Buffer
 			for i, result := range results {
 				content := fmt.Sprintf("#\n# POD %s (%d of %d)\n#\n", result.pod, i+1, len(results))
-				if result.err == nil {
+				switch {
+				case result.err != nil:
+					content += fmt.Sprintf("# ERROR: %s\n", result.err)
+				case options.obfuscate:
+					obfuscatedMetrics, err := obfuscateMetrics(result.metrics)
+					if err != nil {
+						content += fmt.Sprintf("# ERROR %s\n", err)
+					} else {
+						content += string(obfuscatedMetrics)
+					}
+				default:
 					content += string(result.metrics)
-				} else {
-					content += fmt.Sprintf("# ERROR %s\n", result.err)
 				}
+
 				buf.WriteString(content)
 			}
 			fmt.Printf("%s", buf.String())
@@ -99,6 +110,7 @@ func newCmdMetrics() *cobra.Command {
 	}
 
 	cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace of resource")
+	cmd.PersistentFlags().BoolVar(&options.obfuscate, "obfuscate", options.obfuscate, "Obfuscate sensitive information")
 
 	pkgcmd.ConfigureNamespaceFlagCompletion(cmd, []string{"namespace"},
 		kubeconfigPath, impersonate, impersonateGroup, kubeContext)
diff --git a/cli/cmd/metrics_diagnostics_util.go b/cli/cmd/metrics_diagnostics_util.go
index b6e099d93..bb4f6c8bb 100644
--- a/cli/cmd/metrics_diagnostics_util.go
+++ b/cli/cmd/metrics_diagnostics_util.go
@@ -1,6 +1,8 @@
 package cmd
 
 import (
+	"bytes"
+	"crypto/sha256"
 	"fmt"
 	"io/ioutil"
 	"net/http"
@@ -10,6 +12,7 @@ import (
 	"time"
 
 	"github.com/linkerd/linkerd2/pkg/k8s"
+	"github.com/prometheus/common/expfmt"
 	corev1 "k8s.io/api/core/v1"
 )
 
@@ -125,12 +128,13 @@ func getMetrics(
 		}(pod)
 	}
 
+wait:
 	for {
 		select {
 		case result := <-resultChan:
 			results = append(results, result)
 		case <-time.After(waitingTime):
-			break // timed out
+			break wait // timed out
 		}
 		if atomic.LoadInt32(&activeRoutines) == 0 {
 			break
@@ -141,3 +145,43 @@ func getMetrics(
 
 	return results
 }
+
+var obfuscationMap = map[string]struct{}{
+	"authority":     {},
+	"client_id":     {},
+	"server_id":     {},
+	"target_addr":   {},
+	"dst_service":   {},
+	"dst_namespace": {},
+}
+
+func obfuscateMetrics(metrics []byte) ([]byte, error) {
+	reader := bytes.NewReader(metrics)
+
+	var metricsParser expfmt.TextParser
+
+	parsedMetrics, err := metricsParser.TextToMetricFamilies(reader)
+	if err != nil {
+		return nil, err
+	}
+
+	var writer bytes.Buffer
+	for _, v := range parsedMetrics {
+		for _, m := range v.Metric {
+			for _, l := range m.Label {
+				if _, ok := obfuscationMap[l.GetName()]; ok {
+					obfuscatedValue := obfuscate(l.GetValue())
+					l.Value = &obfuscatedValue
+				}
+			}
+		}
+		expfmt.MetricFamilyToText(&writer, v)
+	}
+
+	return writer.Bytes(), nil
+}
+
+func obfuscate(s string) string {
+	hash := sha256.Sum256([]byte(s))
+	return fmt.Sprintf("%x", hash[:4])
+}
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
index 871e6b2ae..e382b044a 100644
--- a/pkg/healthcheck/healthcheck.go
+++ b/pkg/healthcheck/healthcheck.go
@@ -1879,7 +1879,9 @@ func (hc *HealthChecker) checkClusterNetworks(ctx context.Context) error {
 		return &SkipError{Reason: podCIDRUnavailableSkipReason}
 	}
 	if len(badPodCIDRS) > 0 {
-		return fmt.Errorf("node has podCIDR(s) %v which are not contained in the Linkerd clusterNetworks.\n\tTry installing linkerd via --set clusterNetworks=%s", badPodCIDRS, strings.Join(badPodCIDRS, ","))
+		sort.Strings(badPodCIDRS)
+		return fmt.Errorf("node has podCIDR(s) %v which are not contained in the Linkerd clusterNetworks.\n\tTry installing linkerd via --set clusterNetworks=\"%s\"",
+			badPodCIDRS, strings.Join(badPodCIDRS, "\\,"))
 	}
 	return nil
 }


Test patch:
diff --git a/cli/cmd/metrics_diagnostics_util_test.go b/cli/cmd/metrics_diagnostics_util_test.go
index 11aff574f..a26dccc7b 100644
--- a/cli/cmd/metrics_diagnostics_util_test.go
+++ b/cli/cmd/metrics_diagnostics_util_test.go
@@ -4,6 +4,8 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"io/ioutil"
+	"os"
 	"testing"
 
 	"github.com/linkerd/linkerd2/pkg/k8s"
@@ -61,3 +63,38 @@ spec:
 		})
 	}
 }
+
+func Test_obfuscateMetrics(t *testing.T) {
+	tests := []struct {
+		inputFileName  string
+		goldenFileName string
+		wantErr        bool
+	}{
+		{
+			inputFileName:  "obfuscate-diagnostics-proxy-metrics.input",
+			goldenFileName: "obfuscate-diagnostics-proxy-metrics.golden",
+			wantErr:        false,
+		},
+	}
+	for i, tc := range tests {
+		tc := tc
+		t.Run(fmt.Sprintf("%d: %s", i, tc.inputFileName), func(t *testing.T) {
+			file, err := os.Open("testdata/" + tc.inputFileName)
+			if err != nil {
+				t.Errorf("error opening test input file: %v\n", err)
+			}
+
+			fileBytes, err := ioutil.ReadAll(file)
+			if err != nil {
+				t.Errorf("error reading test input file: %v\n", err)
+			}
+
+			got, err := obfuscateMetrics(fileBytes)
+			if (err != nil) != tc.wantErr {
+				t.Errorf("obfuscateMetrics() error = %v, wantErr %v", err, tc.wantErr)
+				return
+			}
+			testDataDiffer.DiffTestdata(t, tc.goldenFileName, string(got))
+		})
+	}
+}
diff --git a/cli/cmd/testdata/obfuscate-diagnostics-proxy-metrics.golden b/cli/cmd/testdata/obfuscate-diagnostics-proxy-metrics.golden
new file mode 100644
index 000000000..94e4e4936
--- /dev/null
+++ b/cli/cmd/testdata/obfuscate-diagnostics-proxy-metrics.golden
@@ -0,0 +1,6 @@
+# HELP inbound_http_authz_allow_total The total number of inbound HTTP requests that were authorized
+# TYPE inbound_http_authz_allow_total counter
+inbound_http_authz_allow_total{target_addr="f21fd977",target_ip="0.0.0.0",target_port="4191",srv_name="default:all-unauthenticated",saz_name="default:all-unauthenticated",tls="no_identity",no_tls_reason="no_tls_from_remote"} 1319
+inbound_http_authz_allow_total{target_addr="d5b2560e",target_ip="10.42.0.63",target_port="8080",srv_name="default:all-unauthenticated",saz_name="default:all-unauthenticated",tls="no_identity",no_tls_reason="no_tls_from_remote"} 7
+inbound_http_authz_allow_total{target_addr="541ef355",target_ip="10.42.0.63",target_port="8090",srv_name="default:all-unauthenticated",saz_name="default:all-unauthenticated",tls="no_identity",no_tls_reason="no_tls_from_remote"} 1
+inbound_http_authz_allow_total{target_addr="541ef355",target_ip="10.42.0.63",target_port="8090",srv_name="default:all-unauthenticated",saz_name="default:all-unauthenticated",tls="true",client_id="b1da0aee"} 3147
diff --git a/cli/cmd/testdata/obfuscate-diagnostics-proxy-metrics.input b/cli/cmd/testdata/obfuscate-diagnostics-proxy-metrics.input
new file mode 100644
index 000000000..e968302a9
--- /dev/null
+++ b/cli/cmd/testdata/obfuscate-diagnostics-proxy-metrics.input
@@ -0,0 +1,6 @@
+# HELP inbound_http_authz_allow_total The total number of inbound HTTP requests that were authorized
+# TYPE inbound_http_authz_allow_total counter
+inbound_http_authz_allow_total{target_addr="0.0.0.0:4191",target_ip="0.0.0.0",target_port="4191",srv_name="default:all-unauthenticated",saz_name="default:all-unauthenticated",tls="no_identity",no_tls_reason="no_tls_from_remote"} 1319
+inbound_http_authz_allow_total{target_addr="10.42.0.63:8080",target_ip="10.42.0.63",target_port="8080",srv_name="default:all-unauthenticated",saz_name="default:all-unauthenticated",tls="no_identity",no_tls_reason="no_tls_from_remote"} 7
+inbound_http_authz_allow_total{target_addr="10.42.0.63:8090",target_ip="10.42.0.63",target_port="8090",srv_name="default:all-unauthenticated",saz_name="default:all-unauthenticated",tls="no_identity",no_tls_reason="no_tls_from_remote"} 1
+inbound_http_authz_allow_total{target_addr="10.42.0.63:8090",target_ip="10.42.0.63",target_port="8090",srv_name="default:all-unauthenticated",saz_name="default:all-unauthenticated",tls="true",client_id="default.default.serviceaccount.identity.linkerd.cluster.local"} 3147
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index 7aa5a95d3..53777ab89 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -1164,6 +1164,14 @@ spec:
   podCIDR: 90.10.90.24/24
 `,
 				`
+apiVersion: v1
+kind: Node
+metadata:
+  name: linkerd-test-ns-identity2
+spec:
+  podCIDR: 242.3.64.0/25
+`,
+				`
 kind: ConfigMap
 apiVersion: v1
 metadata:
@@ -1177,7 +1185,7 @@ data:
 `,
 			},
 			expected: []string{
-				"linkerd-existence cluster networks contains all node podCIDRs: node has podCIDR(s) [90.10.90.24/24] which are not contained in the Linkerd clusterNetworks.\n\tTry installing linkerd via --set clusterNetworks=90.10.90.24/24",
+				"linkerd-existence cluster networks contains all node podCIDRs: node has podCIDR(s) [242.3.64.0/25 90.10.90.24/24] which are not contained in the Linkerd clusterNetworks.\n\tTry installing linkerd via --set clusterNetworks=\"242.3.64.0/25\\,90.10.90.24/24\"",
 			},
 		},
 		{