Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 01:30 UTC
logging: add a regexp filter (#4426)
Kévin Dunglas committed
Nov 23, 2021 at 18:00 UTC
789efa5deef53071b57479d37e4022bf372c4eef
3 files changed
+74
caddytest/integration/caddyfile_adapt/log_filters.txt
+6
@@ -19,6 +19,7 @@ log {
19
ipv4 24
20
ipv6 32
21
}
22
+ request>headers>Regexp regexp secret REDACTED
23
}
24
}
25
}
@@ -55,6 +56,11 @@ log {
56
],
57
"filter": "cookie"
58
},
59
+ "request\u003eheaders\u003eRegexp": {
60
+ "filter": "regexp",
61
+ "regexp": "secret",
62
+ "value": "REDACTED"
63
+ },
64
"request\u003eheaders\u003eServer": {
65
"filter": "delete"
66
},
modules/logging/filters.go
+57
@@ -19,6 +19,7 @@ import (
19
"net"
20
"net/http"
21
"net/url"
22
+ "regexp"
23
"strconv"
24
25
"github.com/caddyserver/caddy/v2"
@@ -32,6 +33,7 @@ func init() {
33
caddy.RegisterModule(IPMaskFilter{})
34
caddy.RegisterModule(QueryFilter{})
35
caddy.RegisterModule(CookieFilter{})
36
+ caddy.RegisterModule(RegexpFilter{})
37
}
38
39
// LogFieldFilter can filter (or manipulate)
@@ -426,6 +428,58 @@ OUTER:
428
return in
429
}
430
431
+// RegexpFilter is a Caddy log field filter that
432
+// replaces the field matching the provided regexp with the indicated string.
433
+type RegexpFilter struct {
434
+ // The regular expression pattern defining what to replace.
435
+ RawRegexp string `json:"regexp,omitempty"`
436
+
437
+ // The value to use as replacement
438
+ Value string `json:"value,omitempty"`
439
+
440
+ regexp *regexp.Regexp
441
+}
442
+
443
+// CaddyModule returns the Caddy module information.
444
+func (RegexpFilter) CaddyModule() caddy.ModuleInfo {
445
+ return caddy.ModuleInfo{
446
+ ID: "caddy.logging.encoders.filter.regexp",
447
+ New: func() caddy.Module { return new(RegexpFilter) },
448
+ }
449
+}
450
+
451
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
452
+func (f *RegexpFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
453
+ for d.Next() {
454
+ if d.NextArg() {
455
+ f.RawRegexp = d.Val()
456
+ }
457
+ if d.NextArg() {
458
+ f.Value = d.Val()
459
+ }
460
+ }
461
+ return nil
462
+}
463
+
464
+// Provision compiles m's regexp.
465
+func (m *RegexpFilter) Provision(ctx caddy.Context) error {
466
+ r, err := regexp.Compile(m.RawRegexp)
467
+ if err != nil {
468
+ return err
469
+ }
470
+
471
+ m.regexp = r
472
+
473
+ return nil
474
+}
475
+
476
+// Filter filters the input field with the replacement value if it matches the regexp.
477
+func (f *RegexpFilter) Filter(in zapcore.Field) zapcore.Field {
478
+ in.String = f.regexp.ReplaceAllString(in.String, f.Value)
479
+
480
+ return in
481
+}
482
+
483
// Interface guards
484
var (
485
_ LogFieldFilter = (*DeleteFilter)(nil)
@@ -433,14 +487,17 @@ var (
487
_ LogFieldFilter = (*IPMaskFilter)(nil)
488
_ LogFieldFilter = (*QueryFilter)(nil)
489
_ LogFieldFilter = (*CookieFilter)(nil)
490
+ _ LogFieldFilter = (*RegexpFilter)(nil)
491
492
_ caddyfile.Unmarshaler = (*DeleteFilter)(nil)
493
_ caddyfile.Unmarshaler = (*ReplaceFilter)(nil)
494
_ caddyfile.Unmarshaler = (*IPMaskFilter)(nil)
495
_ caddyfile.Unmarshaler = (*QueryFilter)(nil)
496
_ caddyfile.Unmarshaler = (*CookieFilter)(nil)
497
+ _ caddyfile.Unmarshaler = (*RegexpFilter)(nil)
498
499
_ caddy.Provisioner = (*IPMaskFilter)(nil)
500
+ _ caddy.Provisioner = (*RegexpFilter)(nil)
501
502
_ caddy.Validator = (*QueryFilter)(nil)
503
)
modules/logging/filters_test.go
+11
@@ -3,6 +3,7 @@ package logging
3
import (
4
"testing"
5
6
+ "github.com/caddyserver/caddy/v2"
7
"go.uber.org/zap/zapcore"
8
)
9
@@ -67,3 +68,13 @@ func TestValidateCookieFilter(t *testing.T) {
68
t.Fatalf("unknown action type must be invalid")
69
}
70
}
71
+
72
+func TestRegexpFilter(t *testing.T) {
73
+ f := RegexpFilter{RawRegexp: `secret`, Value: "REDACTED"}
74
+ f.Provision(caddy.Context{})
75
+
76
+ out := f.Filter(zapcore.Field{String: "foo-secret-bar"})
77
+ if out.String != "foo-REDACTED-bar" {
78
+ t.Fatalf("field has not been filtered: %s", out.String)
79
+ }
80
+}