mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
logging: Implement Caddyfile support for filter encoder (#3578)
* 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
This commit is contained in:
parent
b88e2b6a49
commit
309c1fec62
3 changed files with 225 additions and 9 deletions
69
caddytest/integration/caddyfile_adapt/log_filters.txt
Normal file
69
caddytest/integration/caddyfile_adapt/log_filters.txt
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -330,4 +406,5 @@ func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) e
|
|||
var (
|
||||
_ zapcore.Encoder = (*FilterEncoder)(nil)
|
||||
_ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil)
|
||||
_ caddyfile.Unmarshaler = (*FilterEncoder)(nil)
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 range in CIDR notation.
|
||||
IPv6CIDR int `json:"ipv6_cidr,omitempty"`
|
||||
// The IPv6 mask, as an subnet size CIDR.
|
||||
IPv6MaskRaw int `json:"ipv6_cidr,omitempty"`
|
||||
|
||||
v4Mask net.IPMask
|
||||
v6Mask net.IPMask
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue