logging: add a regexp filter (#4426)

This commit is contained in:
Kévin Dunglas 2021-11-23 18:00:20 +01:00 committed by GitHub
parent 8887adb027
commit 789efa5dee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 0 deletions

View file

@ -19,6 +19,7 @@ log {
ipv4 24 ipv4 24
ipv6 32 ipv6 32
} }
request>headers>Regexp regexp secret REDACTED
} }
} }
} }
@ -55,6 +56,11 @@ log {
], ],
"filter": "cookie" "filter": "cookie"
}, },
"request\u003eheaders\u003eRegexp": {
"filter": "regexp",
"regexp": "secret",
"value": "REDACTED"
},
"request\u003eheaders\u003eServer": { "request\u003eheaders\u003eServer": {
"filter": "delete" "filter": "delete"
}, },

View file

@ -19,6 +19,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"regexp"
"strconv" "strconv"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@ -32,6 +33,7 @@ func init() {
caddy.RegisterModule(IPMaskFilter{}) caddy.RegisterModule(IPMaskFilter{})
caddy.RegisterModule(QueryFilter{}) caddy.RegisterModule(QueryFilter{})
caddy.RegisterModule(CookieFilter{}) caddy.RegisterModule(CookieFilter{})
caddy.RegisterModule(RegexpFilter{})
} }
// LogFieldFilter can filter (or manipulate) // LogFieldFilter can filter (or manipulate)
@ -426,6 +428,58 @@ OUTER:
return in return in
} }
// RegexpFilter is a Caddy log field filter that
// replaces the field matching the provided regexp with the indicated string.
type RegexpFilter struct {
// The regular expression pattern defining what to replace.
RawRegexp string `json:"regexp,omitempty"`
// The value to use as replacement
Value string `json:"value,omitempty"`
regexp *regexp.Regexp
}
// CaddyModule returns the Caddy module information.
func (RegexpFilter) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "caddy.logging.encoders.filter.regexp",
New: func() caddy.Module { return new(RegexpFilter) },
}
}
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func (f *RegexpFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
if d.NextArg() {
f.RawRegexp = d.Val()
}
if d.NextArg() {
f.Value = d.Val()
}
}
return nil
}
// Provision compiles m's regexp.
func (m *RegexpFilter) Provision(ctx caddy.Context) error {
r, err := regexp.Compile(m.RawRegexp)
if err != nil {
return err
}
m.regexp = r
return nil
}
// Filter filters the input field with the replacement value if it matches the regexp.
func (f *RegexpFilter) Filter(in zapcore.Field) zapcore.Field {
in.String = f.regexp.ReplaceAllString(in.String, f.Value)
return in
}
// Interface guards // Interface guards
var ( var (
_ LogFieldFilter = (*DeleteFilter)(nil) _ LogFieldFilter = (*DeleteFilter)(nil)
@ -433,14 +487,17 @@ var (
_ LogFieldFilter = (*IPMaskFilter)(nil) _ LogFieldFilter = (*IPMaskFilter)(nil)
_ LogFieldFilter = (*QueryFilter)(nil) _ LogFieldFilter = (*QueryFilter)(nil)
_ LogFieldFilter = (*CookieFilter)(nil) _ LogFieldFilter = (*CookieFilter)(nil)
_ LogFieldFilter = (*RegexpFilter)(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) _ caddyfile.Unmarshaler = (*CookieFilter)(nil)
_ caddyfile.Unmarshaler = (*RegexpFilter)(nil)
_ caddy.Provisioner = (*IPMaskFilter)(nil) _ caddy.Provisioner = (*IPMaskFilter)(nil)
_ caddy.Provisioner = (*RegexpFilter)(nil)
_ caddy.Validator = (*QueryFilter)(nil) _ caddy.Validator = (*QueryFilter)(nil)
) )

View file

@ -3,6 +3,7 @@ package logging
import ( import (
"testing" "testing"
"github.com/caddyserver/caddy/v2"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
@ -67,3 +68,13 @@ func TestValidateCookieFilter(t *testing.T) {
t.Fatalf("unknown action type must be invalid") t.Fatalf("unknown action type must be invalid")
} }
} }
func TestRegexpFilter(t *testing.T) {
f := RegexpFilter{RawRegexp: `secret`, Value: "REDACTED"}
f.Provision(caddy.Context{})
out := f.Filter(zapcore.Field{String: "foo-secret-bar"})
if out.String != "foo-REDACTED-bar" {
t.Fatalf("field has not been filtered: %s", out.String)
}
}