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 support for hashing data (#4434)
* logging: add support for hashing data * Update modules/logging/filters.go Co-authored-by: wiese <wiese@users.noreply.github.com> * Update modules/logging/filters.go Co-authored-by: wiese <wiese@users.noreply.github.com> Co-authored-by: wiese <wiese@users.noreply.github.com>
Kévin Dunglas committed
Dec 2, 2021 at 21:51 UTC
a1b417c832b4ab3dab9eaa9690e1d07672a949b8
3 files changed
+100
-13
caddytest/integration/caddyfile_adapt/log_filters.txt
+14
@@ -8,18 +8,21 @@ log {
8
uri query {
9
replace foo REDACTED
10
delete bar
11
+ hash baz
12
}
13
request>headers>Authorization replace REDACTED
14
request>headers>Server delete
15
request>headers>Cookie cookie {
16
replace foo REDACTED
17
delete bar
18
+ hash baz
19
}
20
request>remote_ip ip_mask {
21
ipv4 24
22
ipv6 32
23
}
24
request>headers>Regexp regexp secret REDACTED
25
+ request>headers>Hash hash
26
}
27
}
28
}
@@ -52,10 +55,17 @@ log {
55
{
56
"name": "bar",
57
"type": "delete"
58
+ },
59
+ {
60
+ "name": "baz",
61
+ "type": "hash"
62
}
63
],
64
"filter": "cookie"
65
},
66
+ "request\u003eheaders\u003eHash": {
67
+ "filter": "hash"
68
+ },
69
"request\u003eheaders\u003eRegexp": {
70
"filter": "regexp",
71
"regexp": "secret",
@@ -79,6 +89,10 @@ log {
89
{
90
"parameter": "bar",
91
"type": "delete"
92
+ },
93
+ {
94
+ "parameter": "baz",
95
+ "type": "hash"
96
}
97
],
98
"filter": "query"
modules/logging/filters.go
+71
-9
@@ -15,7 +15,9 @@
15
package logging
16
17
import (
18
+ "crypto/sha256"
19
"errors"
20
+ "fmt"
21
"net"
22
"net/http"
23
"net/url"
@@ -34,6 +36,7 @@ func init() {
36
caddy.RegisterModule(QueryFilter{})
37
caddy.RegisterModule(CookieFilter{})
38
caddy.RegisterModule(RegexpFilter{})
39
+ caddy.RegisterModule(HashFilter{})
40
}
41
42
// LogFieldFilter can filter (or manipulate)
@@ -65,6 +68,35 @@ func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field {
68
return in
69
}
70
71
+// hash returns the first 4 bytes of the SHA-256 hash of the given data as hexadecimal
72
+func hash(s string) string {
73
+ return fmt.Sprintf("%.4x", sha256.Sum256([]byte(s)))
74
+}
75
+
76
+// HashFilter is a Caddy log field filter that
77
+// replaces the field with the initial 4 bytes of the SHA-256 hash of the content.
78
+type HashFilter struct {
79
+}
80
+
81
+// CaddyModule returns the Caddy module information.
82
+func (HashFilter) CaddyModule() caddy.ModuleInfo {
83
+ return caddy.ModuleInfo{
84
+ ID: "caddy.logging.encoders.filter.hash",
85
+ New: func() caddy.Module { return new(HashFilter) },
86
+ }
87
+}
88
+
89
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
90
+func (f *HashFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
91
+ return nil
92
+}
93
+
94
+// Filter filters the input field with the replacement value.
95
+func (f *HashFilter) Filter(in zapcore.Field) zapcore.Field {
96
+ in.String = hash(in.String)
97
+ return in
98
+}
99
+
100
// ReplaceFilter is a Caddy log field filter that
101
// replaces the field with the indicated string.
102
type ReplaceFilter struct {
@@ -195,15 +227,19 @@ func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field {
227
type filterAction string
228
229
const (
198
- // Replace value(s) of query parameter(s).
230
+ // Replace value(s).
231
replaceAction filterAction = "replace"
200
- // Delete query parameter(s).
232
+
233
+ // Hash value(s).
234
+ hashAction filterAction = "hash"
235
+
236
+ // Delete.
237
deleteAction filterAction = "delete"
238
)
239
240
func (a filterAction) IsValid() error {
241
switch a {
206
- case replaceAction, deleteAction:
242
+ case replaceAction, deleteAction, hashAction:
243
return nil
244
}
245
@@ -211,7 +247,7 @@ func (a filterAction) IsValid() error {
247
}
248
249
type queryFilterAction struct {
214
- // `replace` to replace the value(s) associated with the parameter(s) or `delete` to remove them entirely.
250
+ // `replace` to replace the value(s) associated with the parameter(s), `hash` to replace them with the 4 initial bytes of the SHA-256 of their content or `delete` to remove them entirely.
251
Type filterAction `json:"type"`
252
253
// The name of the query parameter.
@@ -224,9 +260,9 @@ type queryFilterAction struct {
260
// QueryFilter is a Caddy log field filter that filters
261
// query parameters from a URL.
262
//
227
-// This filter updates the logged URL string to remove or replace query
228
-// parameters containing sensitive data. For instance, it can be used
229
-// to redact any kind of secrets which were passed as query parameters,
263
+// This filter updates the logged URL string to remove, replace or hash
264
+// query parameters containing sensitive data. For instance, it can be
265
+// used to redact any kind of secrets which were passed as query parameters,
266
// such as OAuth access tokens, session IDs, magic link tokens, etc.
267
type QueryFilter struct {
268
// A list of actions to apply to the query parameters of the URL.
@@ -271,6 +307,14 @@ func (m *QueryFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
307
}
308
qfa.Value = d.Val()
309
310
+ case "hash":
311
+ if !d.NextArg() {
312
+ return d.ArgErr()
313
+ }
314
+
315
+ qfa.Type = hashAction
316
+ qfa.Parameter = d.Val()
317
+
318
case "delete":
319
if !d.NextArg() {
320
return d.ArgErr()
@@ -304,6 +348,11 @@ func (m QueryFilter) Filter(in zapcore.Field) zapcore.Field {
348
q[a.Parameter][i] = a.Value
349
}
350
351
+ case hashAction:
352
+ for i := range q[a.Parameter] {
353
+ q[a.Parameter][i] = hash(a.Value)
354
+ }
355
+
356
case deleteAction:
357
q.Del(a.Parameter)
358
}
@@ -316,7 +365,7 @@ func (m QueryFilter) Filter(in zapcore.Field) zapcore.Field {
365
}
366
367
type cookieFilterAction struct {
319
- // `replace` to replace the value of the cookie or `delete` to remove it entirely.
368
+ // `replace` to replace the value of the cookie, `hash` to replace it with the 4 initial bytes of the SHA-256 of its content or `delete` to remove it entirely.
369
Type filterAction `json:"type"`
370
371
// The name of the cookie.
@@ -330,7 +379,7 @@ type cookieFilterAction struct {
379
// cookies.
380
//
381
// This filter updates the logged HTTP header string
333
-// to remove or replace cookies containing sensitive data. For instance,
382
+// to remove, replace or hash cookies containing sensitive data. For instance,
383
// it can be used to redact any kind of secrets, such as session IDs.
384
//
385
// If several actions are configured for the same cookie name, only the first
@@ -378,6 +427,14 @@ func (m *CookieFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
427
}
428
cfa.Value = d.Val()
429
430
+ case "hash":
431
+ if !d.NextArg() {
432
+ return d.ArgErr()
433
+ }
434
+
435
+ cfa.Type = hashAction
436
+ cfa.Name = d.Val()
437
+
438
case "delete":
439
if !d.NextArg() {
440
return d.ArgErr()
@@ -415,6 +472,11 @@ OUTER:
472
transformedRequest.AddCookie(c)
473
continue OUTER
474
475
+ case hashAction:
476
+ c.Value = hash(c.Value)
477
+ transformedRequest.AddCookie(c)
478
+ continue OUTER
479
+
480
case deleteAction:
481
continue OUTER
482
}
modules/logging/filters_test.go
+15
-4
@@ -13,14 +13,15 @@ func TestQueryFilter(t *testing.T) {
13
{replaceAction, "notexist", "REDACTED"},
14
{deleteAction, "bar", ""},
15
{deleteAction, "notexist", ""},
16
+ {hashAction, "hash", ""},
17
}}
18
19
if f.Validate() != nil {
20
t.Fatalf("the filter must be valid")
21
}
22
22
- out := f.Filter(zapcore.Field{String: "/path?foo=a&foo=b&bar=c&bar=d&baz=e"})
23
- if out.String != "/path?baz=e&foo=REDACTED&foo=REDACTED" {
23
+ out := f.Filter(zapcore.Field{String: "/path?foo=a&foo=b&bar=c&bar=d&baz=e&hash=hashed"})
24
+ if out.String != "/path?baz=e&foo=REDACTED&foo=REDACTED&hash=e3b0c442" {
25
t.Fatalf("query parameters have not been filtered: %s", out.String)
26
}
27
}
@@ -45,10 +46,11 @@ func TestCookieFilter(t *testing.T) {
46
f := CookieFilter{[]cookieFilterAction{
47
{replaceAction, "foo", "REDACTED"},
48
{deleteAction, "bar", ""},
49
+ {hashAction, "hash", ""},
50
}}
51
50
- out := f.Filter(zapcore.Field{String: "foo=a; foo=b; bar=c; bar=d; baz=e"})
51
- if out.String != "foo=REDACTED; foo=REDACTED; baz=e" {
52
+ out := f.Filter(zapcore.Field{String: "foo=a; foo=b; bar=c; bar=d; baz=e; hash=hashed"})
53
+ if out.String != "foo=REDACTED; foo=REDACTED; baz=e; hash=1a06df82" {
54
t.Fatalf("cookies have not been filtered: %s", out.String)
55
}
56
}
@@ -78,3 +80,12 @@ func TestRegexpFilter(t *testing.T) {
80
t.Fatalf("field has not been filtered: %s", out.String)
81
}
82
}
83
+
84
+func TestHashFilter(t *testing.T) {
85
+ f := HashFilter{}
86
+
87
+ out := f.Filter(zapcore.Field{String: "foo"})
88
+ if out.String != "2c26b46b" {
89
+ t.Fatalf("field has not been filtered: %s", out.String)
90
+ }
91
+}