From c0efec52d9e0a6f8e9e8c7ca6973845b609adb0e Mon Sep 17 00:00:00 2001 From: Toby Allen Date: Sat, 23 Dec 2017 10:52:11 +0000 Subject: [PATCH] Allow Masking of IP address in Logfile. (#1930) * First working mask * IP Mask working with defaults and empty * add tests for ipmask * Store Mask as setup, some tidying, cleaner flow * Prevent mask from running when directive not present * use custom replacement to store masked ip --- caddyhttp/httpserver/logger.go | 26 +++++- caddyhttp/log/log.go | 11 +++ caddyhttp/log/setup.go | 63 +++++++++++-- caddyhttp/log/setup_test.go | 161 +++++++++++++++++++++++++-------- 4 files changed, 214 insertions(+), 47 deletions(-) diff --git a/caddyhttp/httpserver/logger.go b/caddyhttp/httpserver/logger.go index 6a2f59678..f8ec8b276 100644 --- a/caddyhttp/httpserver/logger.go +++ b/caddyhttp/httpserver/logger.go @@ -18,6 +18,7 @@ import ( "bytes" "io" "log" + "net" "os" "strings" "sync" @@ -37,9 +38,12 @@ var remoteSyslogPrefixes = map[string]string{ type Logger struct { Output string *log.Logger - Roller *LogRoller - writer io.Writer - fileMu *sync.RWMutex + Roller *LogRoller + writer io.Writer + fileMu *sync.RWMutex + V4ipMask net.IPMask + V6ipMask net.IPMask + IPMaskExists bool } // NewTestLogger creates logger suitable for testing purposes @@ -64,6 +68,22 @@ func (l Logger) Printf(format string, args ...interface{}) { l.fileMu.RUnlock() } +func (l Logger) MaskIP(ip string) string { + var reqIP net.IP + // If unable to parse, simply return IP as provided. + reqIP = net.ParseIP(ip) + if reqIP == nil { + return ip + } + + if reqIP.To4() != nil { + return reqIP.Mask(l.V4ipMask).String() + } else { + return reqIP.Mask(l.V6ipMask).String() + } + +} + // Attach binds logger Start and Close functions to // controller's OnStartup and OnShutdown hooks. func (l *Logger) Attach(controller *caddy.Controller) { diff --git a/caddyhttp/log/log.go b/caddyhttp/log/log.go index 1a692b0d3..251334fa9 100644 --- a/caddyhttp/log/log.go +++ b/caddyhttp/log/log.go @@ -17,6 +17,7 @@ package log import ( "fmt" + "net" "net/http" "github.com/mholt/caddy" @@ -66,6 +67,16 @@ func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { // Write log entries for _, e := range rule.Entries { + + // Mask IP Address + if e.Log.IPMaskExists { + hostip, _, err := net.SplitHostPort(r.RemoteAddr) + if err == nil { + maskedIP := e.Log.MaskIP(hostip) + // Overwrite log value with Masked version + rep.Set("remote", maskedIP) + } + } e.Log.Println(rep.Replace(e.Format)) } diff --git a/caddyhttp/log/setup.go b/caddyhttp/log/setup.go index a0e94dc9e..64f2f77ca 100644 --- a/caddyhttp/log/setup.go +++ b/caddyhttp/log/setup.go @@ -15,6 +15,7 @@ package log import ( + "net" "strings" "github.com/mholt/caddy" @@ -47,6 +48,10 @@ func logParse(c *caddy.Controller) ([]*Rule, error) { for c.Next() { args := c.RemainingArgs() + ip4Mask := net.IPMask(net.ParseIP(DefaultIP4Mask).To4()) + ip6Mask := net.IPMask(net.ParseIP(DefaultIP6Mask)) + ipMaskExists := false + var logRoller *httpserver.LogRoller logRoller = httpserver.DefaultLogRoller() @@ -54,14 +59,48 @@ func logParse(c *caddy.Controller) ([]*Rule, error) { what := c.Val() where := c.RemainingArgs() - // only support roller related options inside a block - if !httpserver.IsLogRollerSubdirective(what) { + if what == "ipmask" { + + if len(where) == 0 { + return nil, c.ArgErr() + } + + if where[0] != "" { + ip4MaskStr := where[0] + ipv4 := net.ParseIP(ip4MaskStr).To4() + + if ipv4 == nil { + return nil, c.Err("IPv4 Mask not valid IP Mask Format") + } else { + ip4Mask = net.IPMask(ipv4) + ipMaskExists = true + } + } + + if len(where) > 1 { + + ip6MaskStr := where[1] + ipv6 := net.ParseIP(ip6MaskStr) + + if ipv6 == nil { + return nil, c.Err("IPv6 Mask not valid IP Mask Format") + } else { + ip6Mask = net.IPMask(ipv6) + ipMaskExists = true + } + + } + + } else if httpserver.IsLogRollerSubdirective(what) { + + if err := httpserver.ParseRoller(logRoller, what, where...); err != nil { + return nil, err + } + + } else { return nil, c.ArgErr() } - if err := httpserver.ParseRoller(logRoller, what, where...); err != nil { - return nil, err - } } path := "/" @@ -89,8 +128,11 @@ func logParse(c *caddy.Controller) ([]*Rule, error) { rules = appendEntry(rules, path, &Entry{ Log: &httpserver.Logger{ - Output: output, - Roller: logRoller, + Output: output, + Roller: logRoller, + V4ipMask: ip4Mask, + V6ipMask: ip6Mask, + IPMaskExists: ipMaskExists, }, Format: format, }) @@ -114,3 +156,10 @@ func appendEntry(rules []*Rule, pathScope string, entry *Entry) []*Rule { return rules } + +const ( + // IP Masks that have no effect on IP Address + DefaultIP4Mask = "255.255.255.255" + + DefaultIP6Mask = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" +) diff --git a/caddyhttp/log/setup_test.go b/caddyhttp/log/setup_test.go index 38d34da1d..a1e1b9100 100644 --- a/caddyhttp/log/setup_test.go +++ b/caddyhttp/log/setup_test.go @@ -15,9 +15,9 @@ package log import ( - "testing" - + "net" "reflect" + "testing" "github.com/mholt/caddy" "github.com/mholt/caddy/caddyhttp/httpserver" @@ -47,8 +47,10 @@ func TestSetup(t *testing.T) { } expectedLogger := &httpserver.Logger{ - Output: DefaultLogFilename, - Roller: httpserver.DefaultLogRoller(), + Output: DefaultLogFilename, + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), } if !reflect.DeepEqual(myHandler.Rules[0].Entries[0].Log, expectedLogger) { @@ -72,8 +74,10 @@ func TestLogParse(t *testing.T) { PathScope: "/", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: DefaultLogFilename, - Roller: httpserver.DefaultLogRoller(), + Output: DefaultLogFilename, + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: DefaultLogFormat, }}, @@ -82,8 +86,10 @@ func TestLogParse(t *testing.T) { PathScope: "/", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "log.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: DefaultLogFormat, }}, @@ -92,8 +98,10 @@ func TestLogParse(t *testing.T) { PathScope: "/", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "syslog://127.0.0.1:5000", - Roller: httpserver.DefaultLogRoller(), + Output: "syslog://127.0.0.1:5000", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: DefaultLogFormat, }}, @@ -102,8 +110,10 @@ func TestLogParse(t *testing.T) { PathScope: "/", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "syslog+tcp://127.0.0.1:5000", - Roller: httpserver.DefaultLogRoller(), + Output: "syslog+tcp://127.0.0.1:5000", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: DefaultLogFormat, }}, @@ -112,8 +122,10 @@ func TestLogParse(t *testing.T) { PathScope: "/api", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "log.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: DefaultLogFormat, }}, @@ -122,8 +134,10 @@ func TestLogParse(t *testing.T) { PathScope: "/serve", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "stdout", - Roller: httpserver.DefaultLogRoller(), + Output: "stdout", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: DefaultLogFormat, }}, @@ -132,8 +146,10 @@ func TestLogParse(t *testing.T) { PathScope: "/myapi", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "log.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: CommonLogFormat, }}, @@ -142,8 +158,10 @@ func TestLogParse(t *testing.T) { PathScope: "/myapi", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "log.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: "prefix " + CommonLogFormat + " suffix", }}, @@ -152,8 +170,10 @@ func TestLogParse(t *testing.T) { PathScope: "/test", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "accesslog.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "accesslog.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: CombinedLogFormat, }}, @@ -162,8 +182,10 @@ func TestLogParse(t *testing.T) { PathScope: "/test", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "accesslog.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "accesslog.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: "prefix " + CombinedLogFormat + " suffix", }}, @@ -173,8 +195,10 @@ func TestLogParse(t *testing.T) { PathScope: "/api1", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "log.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: DefaultLogFormat, }}, @@ -182,8 +206,10 @@ func TestLogParse(t *testing.T) { PathScope: "/api2", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "accesslog.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "accesslog.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: CombinedLogFormat, }}, @@ -193,8 +219,10 @@ func TestLogParse(t *testing.T) { PathScope: "/api3", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "stdout", - Roller: httpserver.DefaultLogRoller(), + Output: "stdout", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: "{host}", }}, @@ -202,8 +230,10 @@ func TestLogParse(t *testing.T) { PathScope: "/api4", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "log.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: "{when}", }}, @@ -224,7 +254,59 @@ func TestLogParse(t *testing.T) { MaxBackups: 3, Compress: true, LocalTime: true, - }}, + }, + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), + }, + + Format: DefaultLogFormat, + }}, + }}}, + {`log access0.log { + ipmask 255.255.255.0 + }`, false, []Rule{{ + PathScope: "/", + Entries: []*Entry{{ + Log: &httpserver.Logger{ + Output: "access0.log", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP("255.255.255.0").To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), + IPMaskExists: true, + }, + + Format: DefaultLogFormat, + }}, + }}}, + {`log access1.log { + ipmask "" ffff:ffff:ffff:ff00:: + }`, false, []Rule{{ + PathScope: "/", + Entries: []*Entry{{ + Log: &httpserver.Logger{ + Output: "access1.log", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ff00::")), + IPMaskExists: true, + }, + + Format: DefaultLogFormat, + }}, + }}}, + {`log access2.log { + ipmask 255.255.255.0 ffff:ffff:ffff:ff00:: + }`, false, []Rule{{ + PathScope: "/", + Entries: []*Entry{{ + Log: &httpserver.Logger{ + Output: "access2.log", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP("255.255.255.0").To4()), + V6ipMask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ff00::")), + IPMaskExists: true, + }, + Format: DefaultLogFormat, }}, }}}, @@ -233,14 +315,18 @@ func TestLogParse(t *testing.T) { PathScope: "/", Entries: []*Entry{{ Log: &httpserver.Logger{ - Output: "stdout", - Roller: httpserver.DefaultLogRoller(), + Output: "stdout", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: "{host}", }, { Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), + Output: "log.txt", + Roller: httpserver.DefaultLogRoller(), + V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()), + V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)), }, Format: "{when}", }}, @@ -248,6 +334,7 @@ func TestLogParse(t *testing.T) { {`log access.log { rotate_size 2 rotate_age 10 rotate_keep 3 }`, true, nil}, {`log access.log { rotate_compress invalid }`, true, nil}, {`log access.log { rotate_size }`, true, nil}, + {`log access.log { ipmask }`, true, nil}, {`log access.log { invalid_option 1 }`, true, nil}, {`log / acccess.log "{remote} - [{when}] "{method} {port}" {scheme} {mitm} "`, true, nil}, }