Commit c0efec52 authored by Toby Allen's avatar Toby Allen Committed by GitHub

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
parent a74320bf
...@@ -18,6 +18,7 @@ import ( ...@@ -18,6 +18,7 @@ import (
"bytes" "bytes"
"io" "io"
"log" "log"
"net"
"os" "os"
"strings" "strings"
"sync" "sync"
...@@ -37,9 +38,12 @@ var remoteSyslogPrefixes = map[string]string{ ...@@ -37,9 +38,12 @@ var remoteSyslogPrefixes = map[string]string{
type Logger struct { type Logger struct {
Output string Output string
*log.Logger *log.Logger
Roller *LogRoller Roller *LogRoller
writer io.Writer writer io.Writer
fileMu *sync.RWMutex fileMu *sync.RWMutex
V4ipMask net.IPMask
V6ipMask net.IPMask
IPMaskExists bool
} }
// NewTestLogger creates logger suitable for testing purposes // NewTestLogger creates logger suitable for testing purposes
...@@ -64,6 +68,22 @@ func (l Logger) Printf(format string, args ...interface{}) { ...@@ -64,6 +68,22 @@ func (l Logger) Printf(format string, args ...interface{}) {
l.fileMu.RUnlock() 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 // Attach binds logger Start and Close functions to
// controller's OnStartup and OnShutdown hooks. // controller's OnStartup and OnShutdown hooks.
func (l *Logger) Attach(controller *caddy.Controller) { func (l *Logger) Attach(controller *caddy.Controller) {
......
...@@ -17,6 +17,7 @@ package log ...@@ -17,6 +17,7 @@ package log
import ( import (
"fmt" "fmt"
"net"
"net/http" "net/http"
"github.com/mholt/caddy" "github.com/mholt/caddy"
...@@ -66,6 +67,16 @@ func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { ...@@ -66,6 +67,16 @@ func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
// Write log entries // Write log entries
for _, e := range rule.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)) e.Log.Println(rep.Replace(e.Format))
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
package log package log
import ( import (
"net"
"strings" "strings"
"github.com/mholt/caddy" "github.com/mholt/caddy"
...@@ -47,6 +48,10 @@ func logParse(c *caddy.Controller) ([]*Rule, error) { ...@@ -47,6 +48,10 @@ func logParse(c *caddy.Controller) ([]*Rule, error) {
for c.Next() { for c.Next() {
args := c.RemainingArgs() args := c.RemainingArgs()
ip4Mask := net.IPMask(net.ParseIP(DefaultIP4Mask).To4())
ip6Mask := net.IPMask(net.ParseIP(DefaultIP6Mask))
ipMaskExists := false
var logRoller *httpserver.LogRoller var logRoller *httpserver.LogRoller
logRoller = httpserver.DefaultLogRoller() logRoller = httpserver.DefaultLogRoller()
...@@ -54,14 +59,48 @@ func logParse(c *caddy.Controller) ([]*Rule, error) { ...@@ -54,14 +59,48 @@ func logParse(c *caddy.Controller) ([]*Rule, error) {
what := c.Val() what := c.Val()
where := c.RemainingArgs() where := c.RemainingArgs()
// only support roller related options inside a block if what == "ipmask" {
if !httpserver.IsLogRollerSubdirective(what) {
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() return nil, c.ArgErr()
} }
if err := httpserver.ParseRoller(logRoller, what, where...); err != nil {
return nil, err
}
} }
path := "/" path := "/"
...@@ -89,8 +128,11 @@ func logParse(c *caddy.Controller) ([]*Rule, error) { ...@@ -89,8 +128,11 @@ func logParse(c *caddy.Controller) ([]*Rule, error) {
rules = appendEntry(rules, path, &Entry{ rules = appendEntry(rules, path, &Entry{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: output, Output: output,
Roller: logRoller, Roller: logRoller,
V4ipMask: ip4Mask,
V6ipMask: ip6Mask,
IPMaskExists: ipMaskExists,
}, },
Format: format, Format: format,
}) })
...@@ -114,3 +156,10 @@ func appendEntry(rules []*Rule, pathScope string, entry *Entry) []*Rule { ...@@ -114,3 +156,10 @@ func appendEntry(rules []*Rule, pathScope string, entry *Entry) []*Rule {
return rules 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"
)
...@@ -15,9 +15,9 @@ ...@@ -15,9 +15,9 @@
package log package log
import ( import (
"testing" "net"
"reflect" "reflect"
"testing"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver" "github.com/mholt/caddy/caddyhttp/httpserver"
...@@ -47,8 +47,10 @@ func TestSetup(t *testing.T) { ...@@ -47,8 +47,10 @@ func TestSetup(t *testing.T) {
} }
expectedLogger := &httpserver.Logger{ expectedLogger := &httpserver.Logger{
Output: DefaultLogFilename, Output: DefaultLogFilename,
Roller: httpserver.DefaultLogRoller(), 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) { if !reflect.DeepEqual(myHandler.Rules[0].Entries[0].Log, expectedLogger) {
...@@ -72,8 +74,10 @@ func TestLogParse(t *testing.T) { ...@@ -72,8 +74,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/", PathScope: "/",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: DefaultLogFilename, Output: DefaultLogFilename,
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: DefaultLogFormat, Format: DefaultLogFormat,
}}, }},
...@@ -82,8 +86,10 @@ func TestLogParse(t *testing.T) { ...@@ -82,8 +86,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/", PathScope: "/",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "log.txt", Output: "log.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: DefaultLogFormat, Format: DefaultLogFormat,
}}, }},
...@@ -92,8 +98,10 @@ func TestLogParse(t *testing.T) { ...@@ -92,8 +98,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/", PathScope: "/",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "syslog://127.0.0.1:5000", Output: "syslog://127.0.0.1:5000",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: DefaultLogFormat, Format: DefaultLogFormat,
}}, }},
...@@ -102,8 +110,10 @@ func TestLogParse(t *testing.T) { ...@@ -102,8 +110,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/", PathScope: "/",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "syslog+tcp://127.0.0.1:5000", Output: "syslog+tcp://127.0.0.1:5000",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: DefaultLogFormat, Format: DefaultLogFormat,
}}, }},
...@@ -112,8 +122,10 @@ func TestLogParse(t *testing.T) { ...@@ -112,8 +122,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/api", PathScope: "/api",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "log.txt", Output: "log.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: DefaultLogFormat, Format: DefaultLogFormat,
}}, }},
...@@ -122,8 +134,10 @@ func TestLogParse(t *testing.T) { ...@@ -122,8 +134,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/serve", PathScope: "/serve",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "stdout", Output: "stdout",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: DefaultLogFormat, Format: DefaultLogFormat,
}}, }},
...@@ -132,8 +146,10 @@ func TestLogParse(t *testing.T) { ...@@ -132,8 +146,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/myapi", PathScope: "/myapi",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "log.txt", Output: "log.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: CommonLogFormat, Format: CommonLogFormat,
}}, }},
...@@ -142,8 +158,10 @@ func TestLogParse(t *testing.T) { ...@@ -142,8 +158,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/myapi", PathScope: "/myapi",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "log.txt", Output: "log.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: "prefix " + CommonLogFormat + " suffix", Format: "prefix " + CommonLogFormat + " suffix",
}}, }},
...@@ -152,8 +170,10 @@ func TestLogParse(t *testing.T) { ...@@ -152,8 +170,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/test", PathScope: "/test",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "accesslog.txt", Output: "accesslog.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: CombinedLogFormat, Format: CombinedLogFormat,
}}, }},
...@@ -162,8 +182,10 @@ func TestLogParse(t *testing.T) { ...@@ -162,8 +182,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/test", PathScope: "/test",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "accesslog.txt", Output: "accesslog.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: "prefix " + CombinedLogFormat + " suffix", Format: "prefix " + CombinedLogFormat + " suffix",
}}, }},
...@@ -173,8 +195,10 @@ func TestLogParse(t *testing.T) { ...@@ -173,8 +195,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/api1", PathScope: "/api1",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "log.txt", Output: "log.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: DefaultLogFormat, Format: DefaultLogFormat,
}}, }},
...@@ -182,8 +206,10 @@ func TestLogParse(t *testing.T) { ...@@ -182,8 +206,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/api2", PathScope: "/api2",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "accesslog.txt", Output: "accesslog.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: CombinedLogFormat, Format: CombinedLogFormat,
}}, }},
...@@ -193,8 +219,10 @@ func TestLogParse(t *testing.T) { ...@@ -193,8 +219,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/api3", PathScope: "/api3",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "stdout", Output: "stdout",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: "{host}", Format: "{host}",
}}, }},
...@@ -202,8 +230,10 @@ func TestLogParse(t *testing.T) { ...@@ -202,8 +230,10 @@ func TestLogParse(t *testing.T) {
PathScope: "/api4", PathScope: "/api4",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "log.txt", Output: "log.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: "{when}", Format: "{when}",
}}, }},
...@@ -224,7 +254,59 @@ func TestLogParse(t *testing.T) { ...@@ -224,7 +254,59 @@ func TestLogParse(t *testing.T) {
MaxBackups: 3, MaxBackups: 3,
Compress: true, Compress: true,
LocalTime: 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, Format: DefaultLogFormat,
}}, }},
}}}, }}},
...@@ -233,14 +315,18 @@ func TestLogParse(t *testing.T) { ...@@ -233,14 +315,18 @@ func TestLogParse(t *testing.T) {
PathScope: "/", PathScope: "/",
Entries: []*Entry{{ Entries: []*Entry{{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "stdout", Output: "stdout",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: "{host}", Format: "{host}",
}, { }, {
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: "log.txt", Output: "log.txt",
Roller: httpserver.DefaultLogRoller(), Roller: httpserver.DefaultLogRoller(),
V4ipMask: net.IPMask(net.ParseIP(DefaultIP4Mask).To4()),
V6ipMask: net.IPMask(net.ParseIP(DefaultIP6Mask)),
}, },
Format: "{when}", Format: "{when}",
}}, }},
...@@ -248,6 +334,7 @@ func TestLogParse(t *testing.T) { ...@@ -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_size 2 rotate_age 10 rotate_keep 3 }`, true, nil},
{`log access.log { rotate_compress invalid }`, true, nil}, {`log access.log { rotate_compress invalid }`, true, nil},
{`log access.log { rotate_size }`, true, nil}, {`log access.log { rotate_size }`, true, nil},
{`log access.log { ipmask }`, true, nil},
{`log access.log { invalid_option 1 }`, true, nil}, {`log access.log { invalid_option 1 }`, true, nil},
{`log / acccess.log "{remote} - [{when}] "{method} {port}" {scheme} {mitm} "`, true, nil}, {`log / acccess.log "{remote} - [{when}] "{method} {port}" {scheme} {mitm} "`, true, nil},
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment