mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-02 06:07:21 +01:00
logging: add a filter for cookies (#4425)
* feat(logging): add a filter for cookies * Improve godoc and add validation
This commit is contained in:
parent
bcac2beee7
commit
8887adb027
3 changed files with 163 additions and 0 deletions
|
@ -11,6 +11,10 @@ log {
|
||||||
}
|
}
|
||||||
request>headers>Authorization replace REDACTED
|
request>headers>Authorization replace REDACTED
|
||||||
request>headers>Server delete
|
request>headers>Server delete
|
||||||
|
request>headers>Cookie cookie {
|
||||||
|
replace foo REDACTED
|
||||||
|
delete bar
|
||||||
|
}
|
||||||
request>remote_addr ip_mask {
|
request>remote_addr ip_mask {
|
||||||
ipv4 24
|
ipv4 24
|
||||||
ipv6 32
|
ipv6 32
|
||||||
|
@ -37,6 +41,20 @@ log {
|
||||||
"filter": "replace",
|
"filter": "replace",
|
||||||
"value": "REDACTED"
|
"value": "REDACTED"
|
||||||
},
|
},
|
||||||
|
"request\u003eheaders\u003eCookie": {
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"name": "foo",
|
||||||
|
"type": "replace",
|
||||||
|
"value": "REDACTED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bar",
|
||||||
|
"type": "delete"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filter": "cookie"
|
||||||
|
},
|
||||||
"request\u003eheaders\u003eServer": {
|
"request\u003eheaders\u003eServer": {
|
||||||
"filter": "delete"
|
"filter": "delete"
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,6 +17,7 @@ package logging
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ func init() {
|
||||||
caddy.RegisterModule(ReplaceFilter{})
|
caddy.RegisterModule(ReplaceFilter{})
|
||||||
caddy.RegisterModule(IPMaskFilter{})
|
caddy.RegisterModule(IPMaskFilter{})
|
||||||
caddy.RegisterModule(QueryFilter{})
|
caddy.RegisterModule(QueryFilter{})
|
||||||
|
caddy.RegisterModule(CookieFilter{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogFieldFilter can filter (or manipulate)
|
// LogFieldFilter can filter (or manipulate)
|
||||||
|
@ -311,17 +313,132 @@ func (m QueryFilter) Filter(in zapcore.Field) zapcore.Field {
|
||||||
return in
|
return in
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cookieFilterAction struct {
|
||||||
|
// `replace` to replace the value of the cookie or `delete` to remove it entirely.
|
||||||
|
Type filterAction `json:"type"`
|
||||||
|
|
||||||
|
// The name of the cookie.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// The value to use as replacement if the action is `replace`.
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CookieFilter is a Caddy log field filter that filters
|
||||||
|
// cookies.
|
||||||
|
//
|
||||||
|
// This filter updates the logged HTTP header string
|
||||||
|
// to remove or replace cookies containing sensitive data. For instance,
|
||||||
|
// it can be used to redact any kind of secrets, such as session IDs.
|
||||||
|
//
|
||||||
|
// If several actions are configured for the same cookie name, only the first
|
||||||
|
// will be applied.
|
||||||
|
type CookieFilter struct {
|
||||||
|
// A list of actions to apply to the cookies.
|
||||||
|
Actions []cookieFilterAction `json:"actions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks that action types are correct.
|
||||||
|
func (f *CookieFilter) Validate() error {
|
||||||
|
for _, a := range f.Actions {
|
||||||
|
if err := a.Type.IsValid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (CookieFilter) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "caddy.logging.encoders.filter.cookie",
|
||||||
|
New: func() caddy.Module { return new(CookieFilter) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
|
||||||
|
func (m *CookieFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
for d.Next() {
|
||||||
|
for d.NextBlock(0) {
|
||||||
|
cfa := cookieFilterAction{}
|
||||||
|
switch d.Val() {
|
||||||
|
case "replace":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
cfa.Type = replaceAction
|
||||||
|
cfa.Name = d.Val()
|
||||||
|
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
cfa.Value = d.Val()
|
||||||
|
|
||||||
|
case "delete":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
cfa.Type = deleteAction
|
||||||
|
cfa.Name = d.Val()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Actions = append(m.Actions, cfa)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter filters the input field.
|
||||||
|
func (m CookieFilter) Filter(in zapcore.Field) zapcore.Field {
|
||||||
|
originRequest := http.Request{Header: http.Header{"Cookie": []string{in.String}}}
|
||||||
|
cookies := originRequest.Cookies()
|
||||||
|
transformedRequest := http.Request{Header: make(http.Header)}
|
||||||
|
|
||||||
|
OUTER:
|
||||||
|
for _, c := range cookies {
|
||||||
|
for _, a := range m.Actions {
|
||||||
|
if c.Name != a.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch a.Type {
|
||||||
|
case replaceAction:
|
||||||
|
c.Value = a.Value
|
||||||
|
transformedRequest.AddCookie(c)
|
||||||
|
continue OUTER
|
||||||
|
|
||||||
|
case deleteAction:
|
||||||
|
continue OUTER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transformedRequest.AddCookie(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
in.String = transformedRequest.Header.Get("Cookie")
|
||||||
|
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ LogFieldFilter = (*DeleteFilter)(nil)
|
_ LogFieldFilter = (*DeleteFilter)(nil)
|
||||||
_ LogFieldFilter = (*ReplaceFilter)(nil)
|
_ LogFieldFilter = (*ReplaceFilter)(nil)
|
||||||
_ LogFieldFilter = (*IPMaskFilter)(nil)
|
_ LogFieldFilter = (*IPMaskFilter)(nil)
|
||||||
_ LogFieldFilter = (*QueryFilter)(nil)
|
_ LogFieldFilter = (*QueryFilter)(nil)
|
||||||
|
_ LogFieldFilter = (*CookieFilter)(nil)
|
||||||
|
|
||||||
_ caddyfile.Unmarshaler = (*DeleteFilter)(nil)
|
_ caddyfile.Unmarshaler = (*DeleteFilter)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*ReplaceFilter)(nil)
|
_ caddyfile.Unmarshaler = (*ReplaceFilter)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*IPMaskFilter)(nil)
|
_ caddyfile.Unmarshaler = (*IPMaskFilter)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*QueryFilter)(nil)
|
_ caddyfile.Unmarshaler = (*QueryFilter)(nil)
|
||||||
|
_ caddyfile.Unmarshaler = (*CookieFilter)(nil)
|
||||||
|
|
||||||
_ caddy.Provisioner = (*IPMaskFilter)(nil)
|
_ caddy.Provisioner = (*IPMaskFilter)(nil)
|
||||||
|
|
||||||
|
|
|
@ -39,3 +39,31 @@ func TestValidateQueryFilter(t *testing.T) {
|
||||||
t.Fatalf("unknown action type must be invalid")
|
t.Fatalf("unknown action type must be invalid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCookieFilter(t *testing.T) {
|
||||||
|
f := CookieFilter{[]cookieFilterAction{
|
||||||
|
{replaceAction, "foo", "REDACTED"},
|
||||||
|
{deleteAction, "bar", ""},
|
||||||
|
}}
|
||||||
|
|
||||||
|
out := f.Filter(zapcore.Field{String: "foo=a; foo=b; bar=c; bar=d; baz=e"})
|
||||||
|
if out.String != "foo=REDACTED; foo=REDACTED; baz=e" {
|
||||||
|
t.Fatalf("cookies have not been filtered: %s", out.String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateCookieFilter(t *testing.T) {
|
||||||
|
f := CookieFilter{[]cookieFilterAction{
|
||||||
|
{},
|
||||||
|
}}
|
||||||
|
if f.Validate() == nil {
|
||||||
|
t.Fatalf("empty action type must be invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
f = CookieFilter{[]cookieFilterAction{
|
||||||
|
{Type: "foo"},
|
||||||
|
}}
|
||||||
|
if f.Validate() == nil {
|
||||||
|
t.Fatalf("unknown action type must be invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue