mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04:00 
			
		
		
		
	caddyhttp: Make logging of credential headers opt-in (#4438)
This commit is contained in:
		
							parent
							
								
									8e5aafa5cd
								
							
						
					
					
						commit
						5bf0adad87
					
				| @ -33,15 +33,16 @@ type serverOptions struct { | |||||||
| 	ListenerAddress string | 	ListenerAddress string | ||||||
| 
 | 
 | ||||||
| 	// These will all map 1:1 to the caddyhttp.Server struct | 	// These will all map 1:1 to the caddyhttp.Server struct | ||||||
| 	ListenerWrappersRaw []json.RawMessage | 	ListenerWrappersRaw  []json.RawMessage | ||||||
| 	ReadTimeout         caddy.Duration | 	ReadTimeout          caddy.Duration | ||||||
| 	ReadHeaderTimeout   caddy.Duration | 	ReadHeaderTimeout    caddy.Duration | ||||||
| 	WriteTimeout        caddy.Duration | 	WriteTimeout         caddy.Duration | ||||||
| 	IdleTimeout         caddy.Duration | 	IdleTimeout          caddy.Duration | ||||||
| 	MaxHeaderBytes      int | 	MaxHeaderBytes       int | ||||||
| 	AllowH2C            bool | 	AllowH2C             bool | ||||||
| 	ExperimentalHTTP3   bool | 	ExperimentalHTTP3    bool | ||||||
| 	StrictSNIHost       *bool | 	StrictSNIHost        *bool | ||||||
|  | 	ShouldLogCredentials bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (interface{}, error) { | func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (interface{}, error) { | ||||||
| @ -134,6 +135,12 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (interface{}, error | |||||||
| 				} | 				} | ||||||
| 				serverOpts.MaxHeaderBytes = int(size) | 				serverOpts.MaxHeaderBytes = int(size) | ||||||
| 
 | 
 | ||||||
|  | 			case "log_credentials": | ||||||
|  | 				if d.NextArg() { | ||||||
|  | 					return nil, d.ArgErr() | ||||||
|  | 				} | ||||||
|  | 				serverOpts.ShouldLogCredentials = true | ||||||
|  | 
 | ||||||
| 			case "protocol": | 			case "protocol": | ||||||
| 				for nesting := d.Nesting(); d.NextBlock(nesting); { | 				for nesting := d.Nesting(); d.NextBlock(nesting); { | ||||||
| 					switch d.Val() { | 					switch d.Val() { | ||||||
| @ -222,6 +229,12 @@ func applyServerOptions( | |||||||
| 		server.AllowH2C = opts.AllowH2C | 		server.AllowH2C = opts.AllowH2C | ||||||
| 		server.ExperimentalHTTP3 = opts.ExperimentalHTTP3 | 		server.ExperimentalHTTP3 = opts.ExperimentalHTTP3 | ||||||
| 		server.StrictSNIHost = opts.StrictSNIHost | 		server.StrictSNIHost = opts.StrictSNIHost | ||||||
|  | 		if opts.ShouldLogCredentials { | ||||||
|  | 			if server.Logs == nil { | ||||||
|  | 				server.Logs = &caddyhttp.ServerLogConfig{} | ||||||
|  | 			} | ||||||
|  | 			server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ | |||||||
| 			idle 30s | 			idle 30s | ||||||
| 		} | 		} | ||||||
| 		max_header_size 100MB | 		max_header_size 100MB | ||||||
|  | 		log_credentials | ||||||
| 		protocol { | 		protocol { | ||||||
| 			allow_h2c | 			allow_h2c | ||||||
| 			experimental_http3 | 			experimental_http3 | ||||||
| @ -53,6 +54,9 @@ foo.com { | |||||||
| 						} | 						} | ||||||
| 					], | 					], | ||||||
| 					"strict_sni_host": true, | 					"strict_sni_host": true, | ||||||
|  | 					"logs": { | ||||||
|  | 						"should_log_credentials": true | ||||||
|  | 					}, | ||||||
| 					"experimental_http3": true, | 					"experimental_http3": true, | ||||||
| 					"allow_h2c": true | 					"allow_h2c": true | ||||||
| 				} | 				} | ||||||
|  | |||||||
| @ -24,7 +24,11 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // LoggableHTTPRequest makes an HTTP request loggable with zap.Object(). | // LoggableHTTPRequest makes an HTTP request loggable with zap.Object(). | ||||||
| type LoggableHTTPRequest struct{ *http.Request } | type LoggableHTTPRequest struct { | ||||||
|  | 	*http.Request | ||||||
|  | 
 | ||||||
|  | 	ShouldLogCredentials bool | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. | // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. | ||||||
| func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error { | func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error { | ||||||
| @ -40,7 +44,10 @@ func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error { | |||||||
| 	enc.AddString("method", r.Method) | 	enc.AddString("method", r.Method) | ||||||
| 	enc.AddString("host", r.Host) | 	enc.AddString("host", r.Host) | ||||||
| 	enc.AddString("uri", r.RequestURI) | 	enc.AddString("uri", r.RequestURI) | ||||||
| 	enc.AddObject("headers", LoggableHTTPHeader(r.Header)) | 	enc.AddObject("headers", LoggableHTTPHeader{ | ||||||
|  | 		Header:               r.Header, | ||||||
|  | 		ShouldLogCredentials: r.ShouldLogCredentials, | ||||||
|  | 	}) | ||||||
| 	if r.TLS != nil { | 	if r.TLS != nil { | ||||||
| 		enc.AddObject("tls", LoggableTLSConnState(*r.TLS)) | 		enc.AddObject("tls", LoggableTLSConnState(*r.TLS)) | ||||||
| 	} | 	} | ||||||
| @ -48,19 +55,25 @@ func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LoggableHTTPHeader makes an HTTP header loggable with zap.Object(). | // LoggableHTTPHeader makes an HTTP header loggable with zap.Object(). | ||||||
| // Headers with potentially sensitive information (Cookie, Authorization, | // Headers with potentially sensitive information (Cookie, Set-Cookie, | ||||||
| // and Proxy-Authorization) are logged with empty values. | // Authorization, and Proxy-Authorization) are logged with empty values. | ||||||
| type LoggableHTTPHeader http.Header | type LoggableHTTPHeader struct { | ||||||
|  | 	http.Header | ||||||
|  | 
 | ||||||
|  | 	ShouldLogCredentials bool | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. | // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. | ||||||
| func (h LoggableHTTPHeader) MarshalLogObject(enc zapcore.ObjectEncoder) error { | func (h LoggableHTTPHeader) MarshalLogObject(enc zapcore.ObjectEncoder) error { | ||||||
| 	if h == nil { | 	if h.Header == nil { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	for key, val := range h { | 	for key, val := range h.Header { | ||||||
| 		switch strings.ToLower(key) { | 		if !h.ShouldLogCredentials { | ||||||
| 		case "cookie", "authorization", "proxy-authorization": | 			switch strings.ToLower(key) { | ||||||
| 			val = []string{} | 			case "cookie", "set-cookie", "authorization", "proxy-authorization": | ||||||
|  | 				val = []string{} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 		enc.AddArray(key, LoggableStringArray(val)) | 		enc.AddArray(key, LoggableStringArray(val)) | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -69,6 +69,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) | 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) | ||||||
|  | 	server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server) | ||||||
|  | 	shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials | ||||||
| 
 | 
 | ||||||
| 	// create header for push requests | 	// create header for push requests | ||||||
| 	hdr := h.initializePushHeaders(r, repl) | 	hdr := h.initializePushHeaders(r, repl) | ||||||
| @ -79,7 +81,10 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt | |||||||
| 			zap.String("uri", r.RequestURI), | 			zap.String("uri", r.RequestURI), | ||||||
| 			zap.String("push_method", resource.Method), | 			zap.String("push_method", resource.Method), | ||||||
| 			zap.String("push_target", resource.Target), | 			zap.String("push_target", resource.Target), | ||||||
| 			zap.Object("push_headers", caddyhttp.LoggableHTTPHeader(hdr))) | 			zap.Object("push_headers", caddyhttp.LoggableHTTPHeader{ | ||||||
|  | 				Header:               hdr, | ||||||
|  | 				ShouldLogCredentials: shouldLogCredentials, | ||||||
|  | 			})) | ||||||
| 		err := pusher.Push(repl.ReplaceAll(resource.Target, "."), &http.PushOptions{ | 		err := pusher.Push(repl.ReplaceAll(resource.Target, "."), &http.PushOptions{ | ||||||
| 			Method: resource.Method, | 			Method: resource.Method, | ||||||
| 			Header: hdr, | 			Header: hdr, | ||||||
|  | |||||||
| @ -574,6 +574,9 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl * | |||||||
| 	// point the request to this upstream | 	// point the request to this upstream | ||||||
| 	h.directRequest(req, di) | 	h.directRequest(req, di) | ||||||
| 
 | 
 | ||||||
|  | 	server := req.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server) | ||||||
|  | 	shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials | ||||||
|  | 
 | ||||||
| 	// do the round-trip; emit debug log with values we know are | 	// do the round-trip; emit debug log with values we know are | ||||||
| 	// safe, or if there is no error, emit fuller log entry | 	// safe, or if there is no error, emit fuller log entry | ||||||
| 	start := time.Now() | 	start := time.Now() | ||||||
| @ -582,14 +585,20 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl * | |||||||
| 	logger := h.logger.With( | 	logger := h.logger.With( | ||||||
| 		zap.String("upstream", di.Upstream.String()), | 		zap.String("upstream", di.Upstream.String()), | ||||||
| 		zap.Duration("duration", duration), | 		zap.Duration("duration", duration), | ||||||
| 		zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}), | 		zap.Object("request", caddyhttp.LoggableHTTPRequest{ | ||||||
|  | 			Request:              req, | ||||||
|  | 			ShouldLogCredentials: shouldLogCredentials, | ||||||
|  | 		}), | ||||||
| 	) | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logger.Debug("upstream roundtrip", zap.Error(err)) | 		logger.Debug("upstream roundtrip", zap.Error(err)) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	logger.Debug("upstream roundtrip", | 	logger.Debug("upstream roundtrip", | ||||||
| 		zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)), | 		zap.Object("headers", caddyhttp.LoggableHTTPHeader{ | ||||||
|  | 			Header:               res.Header, | ||||||
|  | 			ShouldLogCredentials: shouldLogCredentials, | ||||||
|  | 		}), | ||||||
| 		zap.Int("status", res.StatusCode)) | 		zap.Int("status", res.StatusCode)) | ||||||
| 
 | 
 | ||||||
| 	// duration until upstream wrote response headers (roundtrip duration) | 	// duration until upstream wrote response headers (roundtrip duration) | ||||||
|  | |||||||
| @ -157,7 +157,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||||
| 	// it enters any handler chain; this is necessary | 	// it enters any handler chain; this is necessary | ||||||
| 	// to capture the original request in case it gets | 	// to capture the original request in case it gets | ||||||
| 	// modified during handling | 	// modified during handling | ||||||
| 	loggableReq := zap.Object("request", LoggableHTTPRequest{r}) | 	shouldLogCredentials := s.Logs != nil && s.Logs.ShouldLogCredentials | ||||||
|  | 	loggableReq := zap.Object("request", LoggableHTTPRequest{ | ||||||
|  | 		Request:              r, | ||||||
|  | 		ShouldLogCredentials: shouldLogCredentials, | ||||||
|  | 	}) | ||||||
| 	errLog := s.errorLogger.With(loggableReq) | 	errLog := s.errorLogger.With(loggableReq) | ||||||
| 
 | 
 | ||||||
| 	var duration time.Duration | 	var duration time.Duration | ||||||
| @ -191,7 +195,10 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||||
| 				zap.Duration("duration", duration), | 				zap.Duration("duration", duration), | ||||||
| 				zap.Int("size", wrec.Size()), | 				zap.Int("size", wrec.Size()), | ||||||
| 				zap.Int("status", wrec.Status()), | 				zap.Int("status", wrec.Status()), | ||||||
| 				zap.Object("resp_headers", LoggableHTTPHeader(wrec.Header())), | 				zap.Object("resp_headers", LoggableHTTPHeader{ | ||||||
|  | 					Header:               wrec.Header(), | ||||||
|  | 					ShouldLogCredentials: shouldLogCredentials, | ||||||
|  | 				}), | ||||||
| 			) | 			) | ||||||
| 		}() | 		}() | ||||||
| 	} | 	} | ||||||
| @ -508,6 +515,12 @@ type ServerLogConfig struct { | |||||||
| 	// If true, requests to any host not appearing in the | 	// If true, requests to any host not appearing in the | ||||||
| 	// LoggerNames (logger_names) map will not be logged. | 	// LoggerNames (logger_names) map will not be logged. | ||||||
| 	SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"` | 	SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"` | ||||||
|  | 
 | ||||||
|  | 	// If true, credentials that are otherwise omitted, will be logged. | ||||||
|  | 	// The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials, | ||||||
|  | 	// and this includes some request and response headers, i.e `Cookie`, | ||||||
|  | 	// `Set-Cookie`, `Authorization`, and `Proxy-Authorization`. | ||||||
|  | 	ShouldLogCredentials bool `json:"should_log_credentials,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // wrapLogger wraps logger in a logger named according to user preferences for the given host. | // wrapLogger wraps logger in a logger named according to user preferences for the given host. | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user