Browse code

middleware: Redact secret data on "secret create"

With debug logging turned on, we currently log the base64-encoded secret
payload.

Change the middleware code to redact this. Since the field is called
"Data", it requires some context-sensitivity. The URI path is examined
to see which route is being invoked.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Aaron Lehmann authored on 2017/06/30 08:04:47
Showing 2 changed files
... ...
@@ -41,7 +41,7 @@ func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWri
41 41
 
42 42
 		var postForm map[string]interface{}
43 43
 		if err := json.Unmarshal(b, &postForm); err == nil {
44
-			maskSecretKeys(postForm)
44
+			maskSecretKeys(postForm, r.RequestURI)
45 45
 			formStr, errMarshal := json.Marshal(postForm)
46 46
 			if errMarshal == nil {
47 47
 				logrus.Debugf("form data: %s", string(formStr))
... ...
@@ -54,13 +54,22 @@ func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWri
54 54
 	}
55 55
 }
56 56
 
57
-func maskSecretKeys(inp interface{}) {
57
+func maskSecretKeys(inp interface{}, path string) {
58
+	// Remove any query string from the path
59
+	idx := strings.Index(path, "?")
60
+	if idx != -1 {
61
+		path = path[:idx]
62
+	}
63
+	// Remove trailing / characters
64
+	path = strings.TrimRight(path, "/")
65
+
58 66
 	if arr, ok := inp.([]interface{}); ok {
59 67
 		for _, f := range arr {
60
-			maskSecretKeys(f)
68
+			maskSecretKeys(f, path)
61 69
 		}
62 70
 		return
63 71
 	}
72
+
64 73
 	if form, ok := inp.(map[string]interface{}); ok {
65 74
 	loop0:
66 75
 		for k, v := range form {
... ...
@@ -70,7 +79,16 @@ func maskSecretKeys(inp interface{}) {
70 70
 					continue loop0
71 71
 				}
72 72
 			}
73
-			maskSecretKeys(v)
73
+			maskSecretKeys(v, path)
74
+		}
75
+
76
+		// Route-specific redactions
77
+		if strings.HasSuffix(path, "/secrets/create") {
78
+			for k := range form {
79
+				if k == "Data" {
80
+					form[k] = "*****"
81
+				}
82
+			}
74 83
 		}
75 84
 	}
76 85
 }
77 86
new file mode 100644
... ...
@@ -0,0 +1,58 @@
0
+package middleware
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/stretchr/testify/assert"
6
+)
7
+
8
+func TestMaskSecretKeys(t *testing.T) {
9
+	tests := []struct {
10
+		path     string
11
+		input    map[string]interface{}
12
+		expected map[string]interface{}
13
+	}{
14
+		{
15
+			path:     "/v1.30/secrets/create",
16
+			input:    map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
17
+			expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
18
+		},
19
+		{
20
+			path:     "/v1.30/secrets/create//",
21
+			input:    map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
22
+			expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
23
+		},
24
+
25
+		{
26
+			path:     "/secrets/create?key=val",
27
+			input:    map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
28
+			expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
29
+		},
30
+		{
31
+			path: "/v1.30/some/other/path",
32
+			input: map[string]interface{}{
33
+				"password": "pass",
34
+				"other": map[string]interface{}{
35
+					"secret":       "secret",
36
+					"jointoken":    "jointoken",
37
+					"unlockkey":    "unlockkey",
38
+					"signingcakey": "signingcakey",
39
+				},
40
+			},
41
+			expected: map[string]interface{}{
42
+				"password": "*****",
43
+				"other": map[string]interface{}{
44
+					"secret":       "*****",
45
+					"jointoken":    "*****",
46
+					"unlockkey":    "*****",
47
+					"signingcakey": "*****",
48
+				},
49
+			},
50
+		},
51
+	}
52
+
53
+	for _, testcase := range tests {
54
+		maskSecretKeys(testcase.input, testcase.path)
55
+		assert.Equal(t, testcase.expected, testcase.input)
56
+	}
57
+}