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>
| ... | ... |
@@ -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 |
+} |