@marcel91200 / caddy Mirror · read-only / commits / 5bf0adad
Fork 0

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.