@marcel91200 / caddy Mirror · read-only / commits / 8887adb0
Fork 0

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 +}