Browse Source

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 8 years ago
parent
commit
3fbc352cbb
2 changed files with 80 additions and 4 deletions
  1. 22 4
      api/server/middleware/debug.go
  2. 58 0
      api/server/middleware/debug_test.go

+ 22 - 4
api/server/middleware/debug.go

@@ -41,7 +41,7 @@ func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWri
 
 		var postForm map[string]interface{}
 		if err := json.Unmarshal(b, &postForm); err == nil {
-			maskSecretKeys(postForm)
+			maskSecretKeys(postForm, r.RequestURI)
 			formStr, errMarshal := json.Marshal(postForm)
 			if errMarshal == nil {
 				logrus.Debugf("form data: %s", string(formStr))
@@ -54,13 +54,22 @@ func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWri
 	}
 }
 
-func maskSecretKeys(inp interface{}) {
+func maskSecretKeys(inp interface{}, path string) {
+	// Remove any query string from the path
+	idx := strings.Index(path, "?")
+	if idx != -1 {
+		path = path[:idx]
+	}
+	// Remove trailing / characters
+	path = strings.TrimRight(path, "/")
+
 	if arr, ok := inp.([]interface{}); ok {
 		for _, f := range arr {
-			maskSecretKeys(f)
+			maskSecretKeys(f, path)
 		}
 		return
 	}
+
 	if form, ok := inp.(map[string]interface{}); ok {
 	loop0:
 		for k, v := range form {
@@ -70,7 +79,16 @@ func maskSecretKeys(inp interface{}) {
 					continue loop0
 				}
 			}
-			maskSecretKeys(v)
+			maskSecretKeys(v, path)
+		}
+
+		// Route-specific redactions
+		if strings.HasSuffix(path, "/secrets/create") {
+			for k := range form {
+				if k == "Data" {
+					form[k] = "*****"
+				}
+			}
 		}
 	}
 }

+ 58 - 0
api/server/middleware/debug_test.go

@@ -0,0 +1,58 @@
+package middleware
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMaskSecretKeys(t *testing.T) {
+	tests := []struct {
+		path     string
+		input    map[string]interface{}
+		expected map[string]interface{}
+	}{
+		{
+			path:     "/v1.30/secrets/create",
+			input:    map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
+			expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
+		},
+		{
+			path:     "/v1.30/secrets/create//",
+			input:    map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
+			expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
+		},
+
+		{
+			path:     "/secrets/create?key=val",
+			input:    map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
+			expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
+		},
+		{
+			path: "/v1.30/some/other/path",
+			input: map[string]interface{}{
+				"password": "pass",
+				"other": map[string]interface{}{
+					"secret":       "secret",
+					"jointoken":    "jointoken",
+					"unlockkey":    "unlockkey",
+					"signingcakey": "signingcakey",
+				},
+			},
+			expected: map[string]interface{}{
+				"password": "*****",
+				"other": map[string]interface{}{
+					"secret":       "*****",
+					"jointoken":    "*****",
+					"unlockkey":    "*****",
+					"signingcakey": "*****",
+				},
+			},
+		},
+	}
+
+	for _, testcase := range tests {
+		maskSecretKeys(testcase.input, testcase.path)
+		assert.Equal(t, testcase.expected, testcase.input)
+	}
+}