mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +03:00
caddyhttp: Make logging of credential headers opt-in (#4438)
This commit is contained in:
parent
8e5aafa5cd
commit
5bf0adad87
6 changed files with 81 additions and 24 deletions
|
@ -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…
Reference in a new issue