1package middleware // import "github.com/docker/docker/api/server/middleware"
2
3import (
4	"bufio"
5	"context"
6	"encoding/json"
7	"io"
8	"net/http"
9	"strings"
10
11	"github.com/docker/docker/api/server/httputils"
12	"github.com/docker/docker/pkg/ioutils"
13	"github.com/sirupsen/logrus"
14)
15
16// DebugRequestMiddleware dumps the request to logger
17func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
18	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
19		logrus.Debugf("Calling %s %s", r.Method, r.RequestURI)
20
21		if r.Method != "POST" {
22			return handler(ctx, w, r, vars)
23		}
24		if err := httputils.CheckForJSON(r); err != nil {
25			return handler(ctx, w, r, vars)
26		}
27		maxBodySize := 4096 // 4KB
28		if r.ContentLength > int64(maxBodySize) {
29			return handler(ctx, w, r, vars)
30		}
31
32		body := r.Body
33		bufReader := bufio.NewReaderSize(body, maxBodySize)
34		r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
35
36		b, err := bufReader.Peek(maxBodySize)
37		if err != io.EOF {
38			// either there was an error reading, or the buffer is full (in which case the request is too large)
39			return handler(ctx, w, r, vars)
40		}
41
42		var postForm map[string]interface{}
43		if err := json.Unmarshal(b, &postForm); err == nil {
44			maskSecretKeys(postForm)
45			formStr, errMarshal := json.Marshal(postForm)
46			if errMarshal == nil {
47				logrus.Debugf("form data: %s", string(formStr))
48			} else {
49				logrus.Debugf("form data: %q", postForm)
50			}
51		}
52
53		return handler(ctx, w, r, vars)
54	}
55}
56
57func maskSecretKeys(inp interface{}) {
58	if arr, ok := inp.([]interface{}); ok {
59		for _, f := range arr {
60			maskSecretKeys(f)
61		}
62		return
63	}
64
65	if form, ok := inp.(map[string]interface{}); ok {
66		scrub := []string{
67			// Note: The Data field contains the base64-encoded secret in 'secret'
68			// and 'config' create and update requests. Currently, no other POST
69			// API endpoints use a data field, so we scrub this field unconditionally.
70			// Change this handling to be conditional if a new endpoint is added
71			// in future where this field should not be scrubbed.
72			"data",
73			"jointoken",
74			"password",
75			"secret",
76			"signingcakey",
77			"unlockkey",
78		}
79	loop0:
80		for k, v := range form {
81			for _, m := range scrub {
82				if strings.EqualFold(m, k) {
83					form[k] = "*****"
84					continue loop0
85				}
86			}
87			maskSecretKeys(v)
88		}
89	}
90}
91