Skip to content

Commit

Permalink
logging: Implement Caddyfile support for filter encoder (#3578)
Browse files Browse the repository at this point in the history
* logging: Implement Caddyfile support for filter encoder

* logging: Add support for parsing IP masks from strings


wip

* logging: Implement Caddyfile support for ip_mask

* logging: Get rid of unnecessary logic to allow strings, not that useful

* logging: Add adapt test
  • Loading branch information
francislavoie authored Sep 15, 2020
1 parent b88e2b6 commit 309c1fe
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 9 deletions.
69 changes: 69 additions & 0 deletions caddytest/integration/caddyfile_adapt/log_filters.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
:80

log {
output stdout
format filter {
wrap console
fields {
request>headers>Authorization delete
request>headers>Server delete
request>remote_addr ip_mask {
ipv4 24
ipv6 32
}
}
}
}
----------
{
"logging": {
"logs": {
"default": {
"exclude": [
"http.log.access.log0"
]
},
"log0": {
"writer": {
"output": "stdout"
},
"encoder": {
"fields": {
"request\u003eheaders\u003eAuthorization": {
"filter": "delete"
},
"request\u003eheaders\u003eServer": {
"filter": "delete"
},
"request\u003eremote_addr": {
"filter": "ip_mask",
"ipv4_cidr": 24,
"ipv6_cidr": 32
}
},
"format": "filter",
"wrap": {
"format": "console"
}
},
"include": [
"http.log.access.log0"
]
}
}
},
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"logs": {
"default_logger_name": "log0"
}
}
}
}
}
}
77 changes: 77 additions & 0 deletions modules/logging/filterencoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"time"

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
Expand Down Expand Up @@ -94,6 +96,80 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
return nil
}

// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
//
// filter {
// wrap <another encoder>
// fields {
// <field> <filter> {
// <filter options>
// }
// }
// }
func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
for d.NextBlock(0) {
switch d.Val() {
case "wrap":
if !d.NextArg() {
return d.ArgErr()
}
moduleName := d.Val()
mod, err := caddy.GetModule("caddy.logging.encoders." + moduleName)
if err != nil {
return d.Errf("getting log encoder module named '%s': %v", moduleName, err)
}
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
return d.Errf("log encoder module '%s' is not a Caddyfile unmarshaler", mod)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
if err != nil {
return err
}
enc, ok := unm.(zapcore.Encoder)
if !ok {
return d.Errf("module %s is not a zapcore.Encoder", mod)
}
fe.WrappedRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil)

case "fields":
for d.NextBlock(1) {
field := d.Val()
if !d.NextArg() {
return d.ArgErr()
}
filterName := d.Val()
mod, err := caddy.GetModule("caddy.logging.encoders.filter." + filterName)
if err != nil {
return d.Errf("getting log filter module named '%s': %v", filterName, err)
}
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
return d.Errf("log encoder module '%s' is not a Caddyfile unmarshaler", mod)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
if err != nil {
return err
}
f, ok := unm.(LogFieldFilter)
if !ok {
return d.Errf("module %s is not a LogFieldFilter", mod)
}
if fe.FieldsRaw == nil {
fe.FieldsRaw = make(map[string]json.RawMessage)
}
fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(f, "filter", filterName, nil)
}

default:
return d.Errf("unrecognized subdirective %s", d.Val())
}
}
}
return nil
}

// AddArray is part of the zapcore.ObjectEncoder interface.
// Array elements do not get filtered.
func (fe FilterEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
Expand Down Expand Up @@ -330,4 +406,5 @@ func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) e
var (
_ zapcore.Encoder = (*FilterEncoder)(nil)
_ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil)
_ caddyfile.Unmarshaler = (*FilterEncoder)(nil)
)
88 changes: 79 additions & 9 deletions modules/logging/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ package logging

import (
"net"
"strconv"

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"go.uber.org/zap/zapcore"
)

Expand All @@ -44,6 +46,11 @@ func (DeleteFilter) CaddyModule() caddy.ModuleInfo {
}
}

// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func (DeleteFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}

// Filter filters the input field.
func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field {
in.Type = zapcore.SkipType
Expand All @@ -53,11 +60,14 @@ func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field {
// IPMaskFilter is a Caddy log field filter that
// masks IP addresses.
type IPMaskFilter struct {
// The IPv4 range in CIDR notation.
IPv4CIDR int `json:"ipv4_cidr,omitempty"`
// The IPv4 mask, as an subnet size CIDR.
IPv4MaskRaw int `json:"ipv4_cidr,omitempty"`

// The IPv6 mask, as an subnet size CIDR.
IPv6MaskRaw int `json:"ipv6_cidr,omitempty"`

// The IPv6 range in CIDR notation.
IPv6CIDR int `json:"ipv6_cidr,omitempty"`
v4Mask net.IPMask
v6Mask net.IPMask
}

// CaddyModule returns the Caddy module information.
Expand All @@ -68,6 +78,58 @@ func (IPMaskFilter) CaddyModule() caddy.ModuleInfo {
}
}

// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func (m *IPMaskFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
for d.NextBlock(0) {
switch d.Val() {
case "ipv4":
if !d.NextArg() {
return d.ArgErr()
}
val, err := strconv.Atoi(d.Val())
if err != nil {
return d.Errf("error parsing %s: %v", d.Val(), err)
}
m.IPv4MaskRaw = val

case "ipv6":
if !d.NextArg() {
return d.ArgErr()
}
val, err := strconv.Atoi(d.Val())
if err != nil {
return d.Errf("error parsing %s: %v", d.Val(), err)
}
m.IPv6MaskRaw = val

default:
return d.Errf("unrecognized subdirective %s", d.Val())
}
}
}
return nil
}

// Provision parses m's IP masks, from integers.
func (m *IPMaskFilter) Provision(ctx caddy.Context) error {
parseRawToMask := func(rawField int, bitLen int) net.IPMask {
if rawField == 0 {
return nil
}

// we assume the int is a subnet size CIDR
// e.g. "16" being equivalent to masking the last
// two bytes of an ipv4 address, like "255.255.0.0"
return net.CIDRMask(rawField, bitLen)
}

m.v4Mask = parseRawToMask(m.IPv4MaskRaw, 32)
m.v6Mask = parseRawToMask(m.IPv6MaskRaw, 128)

return nil
}

// Filter filters the input field.
func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field {
host, port, err := net.SplitHostPort(in.String)
Expand All @@ -78,13 +140,10 @@ func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field {
if ipAddr == nil {
return in
}
bitLen := 32
cidrPrefix := m.IPv4CIDR
mask := m.v4Mask
if ipAddr.To16() != nil {
bitLen = 128
cidrPrefix = m.IPv6CIDR
mask = m.v6Mask
}
mask := net.CIDRMask(cidrPrefix, bitLen)
masked := ipAddr.Mask(mask)
if port == "" {
in.String = masked.String()
Expand All @@ -93,3 +152,14 @@ func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field {
}
return in
}

// Interface guards
var (
_ LogFieldFilter = (*DeleteFilter)(nil)
_ LogFieldFilter = (*IPMaskFilter)(nil)

_ caddyfile.Unmarshaler = (*DeleteFilter)(nil)
_ caddyfile.Unmarshaler = (*IPMaskFilter)(nil)

_ caddy.Provisioner = (*IPMaskFilter)(nil)
)

0 comments on commit 309c1fe

Please sign in to comment.