Skip to content

Commit

Permalink
logging: add a regexp filter (#4426)
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Nov 23, 2021
1 parent 8887adb commit 789efa5
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 0 deletions.
6 changes: 6 additions & 0 deletions caddytest/integration/caddyfile_adapt/log_filters.txt
Expand Up @@ -19,6 +19,7 @@ log {
ipv4 24
ipv6 32
}
request>headers>Regexp regexp secret REDACTED
}
}
}
Expand Down Expand Up @@ -55,6 +56,11 @@ log {
],
"filter": "cookie"
},
"request\u003eheaders\u003eRegexp": {
"filter": "regexp",
"regexp": "secret",
"value": "REDACTED"
},
"request\u003eheaders\u003eServer": {
"filter": "delete"
},
Expand Down
57 changes: 57 additions & 0 deletions modules/logging/filters.go
Expand Up @@ -19,6 +19,7 @@ import (
"net"
"net/http"
"net/url"
"regexp"
"strconv"

"github.com/caddyserver/caddy/v2"
Expand All @@ -32,6 +33,7 @@ func init() {
caddy.RegisterModule(IPMaskFilter{})
caddy.RegisterModule(QueryFilter{})
caddy.RegisterModule(CookieFilter{})
caddy.RegisterModule(RegexpFilter{})
}

// LogFieldFilter can filter (or manipulate)
Expand Down Expand Up @@ -426,21 +428,76 @@ OUTER:
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
var (
_ LogFieldFilter = (*DeleteFilter)(nil)
_ LogFieldFilter = (*ReplaceFilter)(nil)
_ LogFieldFilter = (*IPMaskFilter)(nil)
_ LogFieldFilter = (*QueryFilter)(nil)
_ LogFieldFilter = (*CookieFilter)(nil)
_ LogFieldFilter = (*RegexpFilter)(nil)

_ caddyfile.Unmarshaler = (*DeleteFilter)(nil)
_ caddyfile.Unmarshaler = (*ReplaceFilter)(nil)
_ caddyfile.Unmarshaler = (*IPMaskFilter)(nil)
_ caddyfile.Unmarshaler = (*QueryFilter)(nil)
_ caddyfile.Unmarshaler = (*CookieFilter)(nil)
_ caddyfile.Unmarshaler = (*RegexpFilter)(nil)

_ caddy.Provisioner = (*IPMaskFilter)(nil)
_ caddy.Provisioner = (*RegexpFilter)(nil)

_ caddy.Validator = (*QueryFilter)(nil)
)
11 changes: 11 additions & 0 deletions modules/logging/filters_test.go
Expand Up @@ -3,6 +3,7 @@ package logging
import (
"testing"

"github.com/caddyserver/caddy/v2"
"go.uber.org/zap/zapcore"
)

Expand Down Expand Up @@ -67,3 +68,13 @@ func TestValidateCookieFilter(t *testing.T) {
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)
}
}

0 comments on commit 789efa5

Please sign in to comment.