From f137b82227f7b32b2ca036a89068c806a29a5ac7 Mon Sep 17 00:00:00 2001
From: Aaron Taylor <git@ataylor.io>
Date: Fri, 12 Mar 2021 15:01:34 -0500
Subject: [PATCH] logging: add replace filter for static value replacement
 (#4029)

This filter is intended to be useful in scenarios where you may want to
redact a value with a static string, giving you information that the
field did previously exist and was present, but not revealing the value
itself in the logs.

This was inspired by work on adding more complete support for removing
sensitive values from logs [1]. An example use case would be the
Authorization header in request log output, for which the value should
usually not be logged, but it may be quite useful for debugging to
confirm that the header was present in the request.

[1] https://github.com/caddyserver/caddy/issues/3958
---
 .../caddyfile_adapt/log_filters.txt           |  7 ++--
 modules/logging/filters.go                    | 32 +++++++++++++++++++
 2 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/caddytest/integration/caddyfile_adapt/log_filters.txt b/caddytest/integration/caddyfile_adapt/log_filters.txt
index ab118074c..0949c1d40 100644
--- a/caddytest/integration/caddyfile_adapt/log_filters.txt
+++ b/caddytest/integration/caddyfile_adapt/log_filters.txt
@@ -5,7 +5,7 @@ log {
 	format filter {
 		wrap console
 		fields {
-			request>headers>Authorization delete
+			request>headers>Authorization replace REDACTED
 			request>headers>Server delete
 			request>remote_addr ip_mask {
 				ipv4 24
@@ -30,7 +30,8 @@ log {
 				"encoder": {
 					"fields": {
 						"request\u003eheaders\u003eAuthorization": {
-							"filter": "delete"
+							"filter": "replace",
+							"value": "REDACTED"
 						},
 						"request\u003eheaders\u003eServer": {
 							"filter": "delete"
@@ -66,4 +67,4 @@ log {
 			}
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/modules/logging/filters.go b/modules/logging/filters.go
index a5a917de8..a4e3c738d 100644
--- a/modules/logging/filters.go
+++ b/modules/logging/filters.go
@@ -25,6 +25,7 @@ import (
 
 func init() {
 	caddy.RegisterModule(DeleteFilter{})
+	caddy.RegisterModule(ReplaceFilter{})
 	caddy.RegisterModule(IPMaskFilter{})
 }
 
@@ -57,6 +58,37 @@ func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field {
 	return in
 }
 
+// ReplaceFilter is a Caddy log field filter that
+// replaces the field with the indicated string.
+type ReplaceFilter struct {
+	Value string `json:"value,omitempty"`
+}
+
+// CaddyModule returns the Caddy module information.
+func (ReplaceFilter) CaddyModule() caddy.ModuleInfo {
+	return caddy.ModuleInfo{
+		ID:  "caddy.logging.encoders.filter.replace",
+		New: func() caddy.Module { return new(ReplaceFilter) },
+	}
+}
+
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
+func (f *ReplaceFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+	for d.Next() {
+		if d.NextArg() {
+			f.Value = d.Val()
+		}
+	}
+	return nil
+}
+
+// Filter filters the input field with the replacement value.
+func (f *ReplaceFilter) Filter(in zapcore.Field) zapcore.Field {
+	in.Type = zapcore.StringType
+	in.String = f.Value
+	return in
+}
+
 // IPMaskFilter is a Caddy log field filter that
 // masks IP addresses.
 type IPMaskFilter struct {