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 filter for cookies (#4425)
* feat(logging): add a filter for cookies * Improve godoc and add validation
Kévin Dunglas committed
Nov 23, 2021 at 17:40 UTC
8887adb027982e844965b4707b8595cee5845d54
3 files changed
+163
caddytest/integration/caddyfile_adapt/log_filters.txt
+18
@@ -11,6 +11,10 @@ log {
11
}
12
request>headers>Authorization replace REDACTED
13
request>headers>Server delete
14
+ request>headers>Cookie cookie {
15
+ replace foo REDACTED
16
+ delete bar
17
+ }
18
request>remote_addr ip_mask {
19
ipv4 24
20
ipv6 32
@@ -37,6 +41,20 @@ log {
41
"filter": "replace",
42
"value": "REDACTED"
43
},
44
+ "request\u003eheaders\u003eCookie": {
45
+ "actions": [
46
+ {
47
+ "name": "foo",
48
+ "type": "replace",
49
+ "value": "REDACTED"
50
+ },
51
+ {
52
+ "name": "bar",
53
+ "type": "delete"
54
+ }
55
+ ],
56
+ "filter": "cookie"
57
+ },
58
"request\u003eheaders\u003eServer": {
59
"filter": "delete"
60
},
modules/logging/filters.go
+117
@@ -17,6 +17,7 @@ package logging
17
import (
18
"errors"
19
"net"
20
+ "net/http"
21
"net/url"
22
"strconv"
23
@@ -30,6 +31,7 @@ func init() {
31
caddy.RegisterModule(ReplaceFilter{})
32
caddy.RegisterModule(IPMaskFilter{})
33
caddy.RegisterModule(QueryFilter{})
34
+ caddy.RegisterModule(CookieFilter{})
35
}
36
37
// LogFieldFilter can filter (or manipulate)
@@ -311,17 +313,132 @@ func (m QueryFilter) Filter(in zapcore.Field) zapcore.Field {
313
return in
314
}
315
316
+type cookieFilterAction struct {
317
+ // `replace` to replace the value of the cookie or `delete` to remove it entirely.
318
+ Type filterAction `json:"type"`
319
+
320
+ // The name of the cookie.
321
+ Name string `json:"name"`
322
+
323
+ // The value to use as replacement if the action is `replace`.
324
+ Value string `json:"value,omitempty"`
325
+}
326
+
327
+// CookieFilter is a Caddy log field filter that filters
328
+// cookies.
329
+//
330
+// This filter updates the logged HTTP header string
331
+// to remove or replace cookies containing sensitive data. For instance,
332
+// it can be used to redact any kind of secrets, such as session IDs.
333
+//
334
+// If several actions are configured for the same cookie name, only the first
335
+// will be applied.
336
+type CookieFilter struct {
337
+ // A list of actions to apply to the cookies.
338
+ Actions []cookieFilterAction `json:"actions"`
339
+}
340
+
341
+// Validate checks that action types are correct.
342
+func (f *CookieFilter) Validate() error {
343
+ for _, a := range f.Actions {
344
+ if err := a.Type.IsValid(); err != nil {
345
+ return err
346
+ }
347
+ }
348
+
349
+ return nil
350
+}
351
+
352
+// CaddyModule returns the Caddy module information.
353
+func (CookieFilter) CaddyModule() caddy.ModuleInfo {
354
+ return caddy.ModuleInfo{
355
+ ID: "caddy.logging.encoders.filter.cookie",
356
+ New: func() caddy.Module { return new(CookieFilter) },
357
+ }
358
+}
359
+
360
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
361
+func (m *CookieFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
362
+ for d.Next() {
363
+ for d.NextBlock(0) {
364
+ cfa := cookieFilterAction{}
365
+ switch d.Val() {
366
+ case "replace":
367
+ if !d.NextArg() {
368
+ return d.ArgErr()
369
+ }
370
+
371
+ cfa.Type = replaceAction
372
+ cfa.Name = d.Val()
373
+
374
+ if !d.NextArg() {
375
+ return d.ArgErr()
376
+ }
377
+ cfa.Value = d.Val()
378
+
379
+ case "delete":
380
+ if !d.NextArg() {
381
+ return d.ArgErr()
382
+ }
383
+
384
+ cfa.Type = deleteAction
385
+ cfa.Name = d.Val()
386
+
387
+ default:
388
+ return d.Errf("unrecognized subdirective %s", d.Val())
389
+ }
390
+
391
+ m.Actions = append(m.Actions, cfa)
392
+ }
393
+ }
394
+ return nil
395
+}
396
+
397
+// Filter filters the input field.
398
+func (m CookieFilter) Filter(in zapcore.Field) zapcore.Field {
399
+ originRequest := http.Request{Header: http.Header{"Cookie": []string{in.String}}}
400
+ cookies := originRequest.Cookies()
401
+ transformedRequest := http.Request{Header: make(http.Header)}
402
+
403
+OUTER:
404
+ for _, c := range cookies {
405
+ for _, a := range m.Actions {
406
+ if c.Name != a.Name {
407
+ continue
408
+ }
409
+
410
+ switch a.Type {
411
+ case replaceAction:
412
+ c.Value = a.Value
413
+ transformedRequest.AddCookie(c)
414
+ continue OUTER
415
+
416
+ case deleteAction:
417
+ continue OUTER
418
+ }
419
+ }
420
+
421
+ transformedRequest.AddCookie(c)
422
+ }
423
+
424
+ in.String = transformedRequest.Header.Get("Cookie")
425
+
426
+ return in
427
+}
428
+
429
// Interface guards
430
var (
431
_ LogFieldFilter = (*DeleteFilter)(nil)
432
_ LogFieldFilter = (*ReplaceFilter)(nil)
433
_ LogFieldFilter = (*IPMaskFilter)(nil)
434
_ LogFieldFilter = (*QueryFilter)(nil)
435
+ _ LogFieldFilter = (*CookieFilter)(nil)
436
437
_ caddyfile.Unmarshaler = (*DeleteFilter)(nil)
438
_ caddyfile.Unmarshaler = (*ReplaceFilter)(nil)
439
_ caddyfile.Unmarshaler = (*IPMaskFilter)(nil)
440
_ caddyfile.Unmarshaler = (*QueryFilter)(nil)
441
+ _ caddyfile.Unmarshaler = (*CookieFilter)(nil)
442
443
_ caddy.Provisioner = (*IPMaskFilter)(nil)
444
modules/logging/filters_test.go
+28
@@ -39,3 +39,31 @@ func TestValidateQueryFilter(t *testing.T) {
39
t.Fatalf("unknown action type must be invalid")
40
}
41
}
42
+
43
+func TestCookieFilter(t *testing.T) {
44
+ f := CookieFilter{[]cookieFilterAction{
45
+ {replaceAction, "foo", "REDACTED"},
46
+ {deleteAction, "bar", ""},
47
+ }}
48
+
49
+ out := f.Filter(zapcore.Field{String: "foo=a; foo=b; bar=c; bar=d; baz=e"})
50
+ if out.String != "foo=REDACTED; foo=REDACTED; baz=e" {
51
+ t.Fatalf("cookies have not been filtered: %s", out.String)
52
+ }
53
+}
54
+
55
+func TestValidateCookieFilter(t *testing.T) {
56
+ f := CookieFilter{[]cookieFilterAction{
57
+ {},
58
+ }}
59
+ if f.Validate() == nil {
60
+ t.Fatalf("empty action type must be invalid")
61
+ }
62
+
63
+ f = CookieFilter{[]cookieFilterAction{
64
+ {Type: "foo"},
65
+ }}
66
+ if f.Validate() == nil {
67
+ t.Fatalf("unknown action type must be invalid")
68
+ }
69
+}