Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 01:30 UTC
caddyhttp: Make logging of credential headers opt-in (#4438)
Francis Lavoie committed
Dec 2, 2021 at 15:26 UTC
5bf0adad8748e96e10529d5fc5777afc9236a7b5
6 files changed
+81
-24
caddyconfig/httpcaddyfile/serveroptions.go
+22
-9
@@ -33,15 +33,16 @@ type serverOptions struct {
33
ListenerAddress string
34
35
// These will all map 1:1 to the caddyhttp.Server struct
36
- ListenerWrappersRaw []json.RawMessage
37
- ReadTimeout caddy.Duration
38
- ReadHeaderTimeout caddy.Duration
39
- WriteTimeout caddy.Duration
40
- IdleTimeout caddy.Duration
41
- MaxHeaderBytes int
42
- AllowH2C bool
43
- ExperimentalHTTP3 bool
44
- StrictSNIHost *bool
36
+ ListenerWrappersRaw []json.RawMessage
37
+ ReadTimeout caddy.Duration
38
+ ReadHeaderTimeout caddy.Duration
39
+ WriteTimeout caddy.Duration
40
+ IdleTimeout caddy.Duration
41
+ MaxHeaderBytes int
42
+ AllowH2C bool
43
+ ExperimentalHTTP3 bool
44
+ StrictSNIHost *bool
45
+ ShouldLogCredentials bool
46
}
47
48
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (interface{}, error) {
@@ -134,6 +135,12 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (interface{}, error
135
}
136
serverOpts.MaxHeaderBytes = int(size)
137
138
+ case "log_credentials":
139
+ if d.NextArg() {
140
+ return nil, d.ArgErr()
141
+ }
142
+ serverOpts.ShouldLogCredentials = true
143
+
144
case "protocol":
145
for nesting := d.Nesting(); d.NextBlock(nesting); {
146
switch d.Val() {
@@ -222,6 +229,12 @@ func applyServerOptions(
229
server.AllowH2C = opts.AllowH2C
230
server.ExperimentalHTTP3 = opts.ExperimentalHTTP3
231
server.StrictSNIHost = opts.StrictSNIHost
232
+ if opts.ShouldLogCredentials {
233
+ if server.Logs == nil {
234
+ server.Logs = &caddyhttp.ServerLogConfig{}
235
+ }
236
+ server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials
237
+ }
238
}
239
240
return nil
caddytest/integration/caddyfile_adapt/global_server_options_single.txt
+4
@@ -10,6 +10,7 @@
10
idle 30s
11
}
12
max_header_size 100MB
13
+ log_credentials
14
protocol {
15
allow_h2c
16
experimental_http3
@@ -53,6 +54,9 @@ foo.com {
54
}
55
],
56
"strict_sni_host": true,
57
+ "logs": {
58
+ "should_log_credentials": true
59
+ },
60
"experimental_http3": true,
61
"allow_h2c": true
62
}
modules/caddyhttp/marshalers.go
+23
-10
@@ -24,7 +24,11 @@ import (
24
)
25
26
// LoggableHTTPRequest makes an HTTP request loggable with zap.Object().
27
-type LoggableHTTPRequest struct{ *http.Request }
27
+type LoggableHTTPRequest struct {
28
+ *http.Request
29
+
30
+ ShouldLogCredentials bool
31
+}
32
33
// MarshalLogObject satisfies the zapcore.ObjectMarshaler interface.
34
func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error {
@@ -40,7 +44,10 @@ func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error {
44
enc.AddString("method", r.Method)
45
enc.AddString("host", r.Host)
46
enc.AddString("uri", r.RequestURI)
43
- enc.AddObject("headers", LoggableHTTPHeader(r.Header))
47
+ enc.AddObject("headers", LoggableHTTPHeader{
48
+ Header: r.Header,
49
+ ShouldLogCredentials: r.ShouldLogCredentials,
50
+ })
51
if r.TLS != nil {
52
enc.AddObject("tls", LoggableTLSConnState(*r.TLS))
53
}
@@ -48,19 +55,25 @@ func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error {
55
}
56
57
// LoggableHTTPHeader makes an HTTP header loggable with zap.Object().
51
-// Headers with potentially sensitive information (Cookie, Authorization,
52
-// and Proxy-Authorization) are logged with empty values.
53
-type LoggableHTTPHeader http.Header
58
+// Headers with potentially sensitive information (Cookie, Set-Cookie,
59
+// Authorization, and Proxy-Authorization) are logged with empty values.
60
+type LoggableHTTPHeader struct {
61
+ http.Header
62
+
63
+ ShouldLogCredentials bool
64
+}
65
66
// MarshalLogObject satisfies the zapcore.ObjectMarshaler interface.
67
func (h LoggableHTTPHeader) MarshalLogObject(enc zapcore.ObjectEncoder) error {
57
- if h == nil {
68
+ if h.Header == nil {
69
return nil
70
}
60
- for key, val := range h {
61
- switch strings.ToLower(key) {
62
- case "cookie", "authorization", "proxy-authorization":
63
- val = []string{}
71
+ for key, val := range h.Header {
72
+ if !h.ShouldLogCredentials {
73
+ switch strings.ToLower(key) {
74
+ case "cookie", "set-cookie", "authorization", "proxy-authorization":
75
+ val = []string{}
76
+ }
77
}
78
enc.AddArray(key, LoggableStringArray(val))
79
}
modules/caddyhttp/push/handler.go
+6
-1
@@ -69,6 +69,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt
69
}
70
71
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
72
+ server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server)
73
+ shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials
74
75
// create header for push requests
76
hdr := h.initializePushHeaders(r, repl)
@@ -79,7 +81,10 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt
81
zap.String("uri", r.RequestURI),
82
zap.String("push_method", resource.Method),
83
zap.String("push_target", resource.Target),
82
- zap.Object("push_headers", caddyhttp.LoggableHTTPHeader(hdr)))
84
+ zap.Object("push_headers", caddyhttp.LoggableHTTPHeader{
85
+ Header: hdr,
86
+ ShouldLogCredentials: shouldLogCredentials,
87
+ }))
88
err := pusher.Push(repl.ReplaceAll(resource.Target, "."), &http.PushOptions{
89
Method: resource.Method,
90
Header: hdr,
modules/caddyhttp/reverseproxy/reverseproxy.go
+11
-2
@@ -574,6 +574,9 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl *
574
// point the request to this upstream
575
h.directRequest(req, di)
576
577
+ server := req.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server)
578
+ shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials
579
+
580
// do the round-trip; emit debug log with values we know are
581
// safe, or if there is no error, emit fuller log entry
582
start := time.Now()
@@ -582,14 +585,20 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl *
585
logger := h.logger.With(
586
zap.String("upstream", di.Upstream.String()),
587
zap.Duration("duration", duration),
585
- zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}),
588
+ zap.Object("request", caddyhttp.LoggableHTTPRequest{
589
+ Request: req,
590
+ ShouldLogCredentials: shouldLogCredentials,
591
+ }),
592
)
593
if err != nil {
594
logger.Debug("upstream roundtrip", zap.Error(err))
595
return err
596
}
597
logger.Debug("upstream roundtrip",
592
- zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)),
598
+ zap.Object("headers", caddyhttp.LoggableHTTPHeader{
599
+ Header: res.Header,
600
+ ShouldLogCredentials: shouldLogCredentials,
601
+ }),
602
zap.Int("status", res.StatusCode))
603
604
// duration until upstream wrote response headers (roundtrip duration)
modules/caddyhttp/server.go
+15
-2
@@ -157,7 +157,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
157
// it enters any handler chain; this is necessary
158
// to capture the original request in case it gets
159
// modified during handling
160
- loggableReq := zap.Object("request", LoggableHTTPRequest{r})
160
+ shouldLogCredentials := s.Logs != nil && s.Logs.ShouldLogCredentials
161
+ loggableReq := zap.Object("request", LoggableHTTPRequest{
162
+ Request: r,
163
+ ShouldLogCredentials: shouldLogCredentials,
164
+ })
165
errLog := s.errorLogger.With(loggableReq)
166
167
var duration time.Duration
@@ -191,7 +195,10 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
195
zap.Duration("duration", duration),
196
zap.Int("size", wrec.Size()),
197
zap.Int("status", wrec.Status()),
194
- zap.Object("resp_headers", LoggableHTTPHeader(wrec.Header())),
198
+ zap.Object("resp_headers", LoggableHTTPHeader{
199
+ Header: wrec.Header(),
200
+ ShouldLogCredentials: shouldLogCredentials,
201
+ }),
202
)
203
}()
204
}
@@ -508,6 +515,12 @@ type ServerLogConfig struct {
515
// If true, requests to any host not appearing in the
516
// LoggerNames (logger_names) map will not be logged.
517
SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"`
518
+
519
+ // If true, credentials that are otherwise omitted, will be logged.
520
+ // The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials,
521
+ // and this includes some request and response headers, i.e `Cookie`,
522
+ // `Set-Cookie`, `Authorization`, and `Proxy-Authorization`.
523
+ ShouldLogCredentials bool `json:"should_log_credentials,omitempty"`
524
}
525
526
// wrapLogger wraps logger in a logger named according to user preferences for the given host.