2019-10-28 23:39:37 +03:00
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logging
import (
2021-12-02 23:51:37 +03:00
"crypto/sha256"
2021-11-23 12:01:43 +03:00
"errors"
2021-12-02 23:51:37 +03:00
"fmt"
2019-10-28 23:39:37 +03:00
"net"
2021-11-23 19:40:20 +03:00
"net/http"
2021-11-23 12:01:43 +03:00
"net/url"
2021-11-23 20:00:20 +03:00
"regexp"
2020-09-15 21:37:41 +03:00
"strconv"
2022-10-05 08:21:23 +03:00
"strings"
2019-10-28 23:39:37 +03:00
2023-08-14 18:41:15 +03:00
"go.uber.org/zap/zapcore"
2019-10-28 23:39:37 +03:00
"github.com/caddyserver/caddy/v2"
2020-09-15 21:37:41 +03:00
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
2022-08-09 04:11:02 +03:00
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
2019-10-28 23:39:37 +03:00
)
func init ( ) {
caddy . RegisterModule ( DeleteFilter { } )
2022-04-28 18:38:44 +03:00
caddy . RegisterModule ( HashFilter { } )
2021-03-12 23:01:34 +03:00
caddy . RegisterModule ( ReplaceFilter { } )
2019-10-28 23:39:37 +03:00
caddy . RegisterModule ( IPMaskFilter { } )
2021-11-23 12:01:43 +03:00
caddy . RegisterModule ( QueryFilter { } )
2021-11-23 19:40:20 +03:00
caddy . RegisterModule ( CookieFilter { } )
2021-11-23 20:00:20 +03:00
caddy . RegisterModule ( RegexpFilter { } )
2022-04-28 18:38:44 +03:00
caddy . RegisterModule ( RenameFilter { } )
2019-10-28 23:39:37 +03:00
}
// LogFieldFilter can filter (or manipulate)
2020-05-29 20:49:21 +03:00
// a field in a log entry.
2019-10-28 23:39:37 +03:00
type LogFieldFilter interface {
Filter ( zapcore . Field ) zapcore . Field
}
// DeleteFilter is a Caddy log field filter that
// deletes the field.
type DeleteFilter struct { }
// CaddyModule returns the Caddy module information.
func ( DeleteFilter ) CaddyModule ( ) caddy . ModuleInfo {
return caddy . ModuleInfo {
2019-12-10 23:36:46 +03:00
ID : "caddy.logging.encoders.filter.delete" ,
New : func ( ) caddy . Module { return new ( DeleteFilter ) } ,
2019-10-28 23:39:37 +03:00
}
}
2020-09-15 21:37:41 +03:00
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func ( DeleteFilter ) UnmarshalCaddyfile ( d * caddyfile . Dispenser ) error {
return nil
}
2019-10-28 23:39:37 +03:00
// Filter filters the input field.
func ( DeleteFilter ) Filter ( in zapcore . Field ) zapcore . Field {
in . Type = zapcore . SkipType
return in
}
2021-12-02 23:51:37 +03:00
// hash returns the first 4 bytes of the SHA-256 hash of the given data as hexadecimal
func hash ( s string ) string {
return fmt . Sprintf ( "%.4x" , sha256 . Sum256 ( [ ] byte ( s ) ) )
}
// HashFilter is a Caddy log field filter that
2022-10-05 08:21:23 +03:00
// replaces the field with the initial 4 bytes
// of the SHA-256 hash of the content. Operates
// on string fields, or on arrays of strings
// where each string is hashed.
2023-08-07 22:40:31 +03:00
type HashFilter struct { }
2021-12-02 23:51:37 +03:00
// CaddyModule returns the Caddy module information.
func ( HashFilter ) CaddyModule ( ) caddy . ModuleInfo {
return caddy . ModuleInfo {
ID : "caddy.logging.encoders.filter.hash" ,
New : func ( ) caddy . Module { return new ( HashFilter ) } ,
}
}
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func ( f * HashFilter ) UnmarshalCaddyfile ( d * caddyfile . Dispenser ) error {
return nil
}
// Filter filters the input field with the replacement value.
func ( f * HashFilter ) Filter ( in zapcore . Field ) zapcore . Field {
2022-10-05 08:21:23 +03:00
if array , ok := in . Interface . ( caddyhttp . LoggableStringArray ) ; ok {
2023-08-29 20:41:39 +03:00
newArray := make ( caddyhttp . LoggableStringArray , len ( array ) )
2022-10-05 08:21:23 +03:00
for i , s := range array {
2023-08-29 20:41:39 +03:00
newArray [ i ] = hash ( s )
2022-10-05 08:21:23 +03:00
}
2023-08-29 20:41:39 +03:00
in . Interface = newArray
2022-10-05 08:21:23 +03:00
} else {
in . String = hash ( in . String )
}
2023-08-29 20:41:39 +03:00
2021-12-02 23:51:37 +03:00
return in
}
2021-03-12 23:01:34 +03:00
// 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
}
2019-10-28 23:39:37 +03:00
// IPMaskFilter is a Caddy log field filter that
2022-10-05 08:21:23 +03:00
// masks IP addresses in a string, or in an array
// of strings. The string may be a comma separated
// list of IP addresses, where all of the values
// will be masked.
2019-10-28 23:39:37 +03:00
type IPMaskFilter struct {
2020-09-15 21:37:41 +03:00
// 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" `
2019-12-10 23:36:46 +03:00
2020-09-15 21:37:41 +03:00
v4Mask net . IPMask
v6Mask net . IPMask
2019-10-28 23:39:37 +03:00
}
// CaddyModule returns the Caddy module information.
func ( IPMaskFilter ) CaddyModule ( ) caddy . ModuleInfo {
return caddy . ModuleInfo {
2019-12-10 23:36:46 +03:00
ID : "caddy.logging.encoders.filter.ip_mask" ,
New : func ( ) caddy . Module { return new ( IPMaskFilter ) } ,
2019-10-28 23:39:37 +03:00
}
}
2020-09-15 21:37:41 +03:00
// 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
}
2019-10-28 23:39:37 +03:00
// Filter filters the input field.
func ( m IPMaskFilter ) Filter ( in zapcore . Field ) zapcore . Field {
2022-10-05 08:21:23 +03:00
if array , ok := in . Interface . ( caddyhttp . LoggableStringArray ) ; ok {
2023-08-29 20:41:39 +03:00
newArray := make ( caddyhttp . LoggableStringArray , len ( array ) )
2022-10-05 08:21:23 +03:00
for i , s := range array {
2023-08-29 20:41:39 +03:00
newArray [ i ] = m . mask ( s )
2022-10-05 08:21:23 +03:00
}
2023-08-29 20:41:39 +03:00
in . Interface = newArray
2019-10-28 23:39:37 +03:00
} else {
2022-10-05 08:21:23 +03:00
in . String = m . mask ( in . String )
2019-10-28 23:39:37 +03:00
}
2022-10-05 08:21:23 +03:00
2019-10-28 23:39:37 +03:00
return in
}
2020-09-15 21:37:41 +03:00
2022-10-05 08:21:23 +03:00
func ( m IPMaskFilter ) mask ( s string ) string {
output := ""
for _ , value := range strings . Split ( s , "," ) {
value = strings . TrimSpace ( value )
host , port , err := net . SplitHostPort ( value )
if err != nil {
host = value // assume whole thing was IP address
}
ipAddr := net . ParseIP ( host )
if ipAddr == nil {
output += value + ", "
continue
}
mask := m . v4Mask
if ipAddr . To4 ( ) == nil {
mask = m . v6Mask
}
masked := ipAddr . Mask ( mask )
if port == "" {
output += masked . String ( ) + ", "
continue
}
output += net . JoinHostPort ( masked . String ( ) , port ) + ", "
}
return strings . TrimSuffix ( output , ", " )
}
2021-11-23 12:01:43 +03:00
type filterAction string
const (
2021-12-02 23:51:37 +03:00
// Replace value(s).
2021-11-23 12:01:43 +03:00
replaceAction filterAction = "replace"
2021-12-02 23:51:37 +03:00
// Hash value(s).
hashAction filterAction = "hash"
// Delete.
2021-11-23 12:01:43 +03:00
deleteAction filterAction = "delete"
)
func ( a filterAction ) IsValid ( ) error {
switch a {
2021-12-02 23:51:37 +03:00
case replaceAction , deleteAction , hashAction :
2021-11-23 12:01:43 +03:00
return nil
}
return errors . New ( "invalid action type" )
}
type queryFilterAction struct {
2021-12-02 23:51:37 +03:00
// `replace` to replace the value(s) associated with the parameter(s), `hash` to replace them with the 4 initial bytes of the SHA-256 of their content or `delete` to remove them entirely.
2021-11-23 12:01:43 +03:00
Type filterAction ` json:"type" `
// The name of the query parameter.
Parameter string ` json:"parameter" `
// The value to use as replacement if the action is `replace`.
Value string ` json:"value,omitempty" `
}
// QueryFilter is a Caddy log field filter that filters
// query parameters from a URL.
//
2021-12-02 23:51:37 +03:00
// This filter updates the logged URL string to remove, replace or hash
// query parameters containing sensitive data. For instance, it can be
// used to redact any kind of secrets which were passed as query parameters,
2021-11-23 12:01:43 +03:00
// such as OAuth access tokens, session IDs, magic link tokens, etc.
type QueryFilter struct {
// A list of actions to apply to the query parameters of the URL.
Actions [ ] queryFilterAction ` json:"actions" `
}
// Validate checks that action types are correct.
func ( f * QueryFilter ) Validate ( ) error {
for _ , a := range f . Actions {
if err := a . Type . IsValid ( ) ; err != nil {
return err
}
}
return nil
}
// CaddyModule returns the Caddy module information.
func ( QueryFilter ) CaddyModule ( ) caddy . ModuleInfo {
return caddy . ModuleInfo {
ID : "caddy.logging.encoders.filter.query" ,
New : func ( ) caddy . Module { return new ( QueryFilter ) } ,
}
}
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func ( m * QueryFilter ) UnmarshalCaddyfile ( d * caddyfile . Dispenser ) error {
for d . Next ( ) {
for d . NextBlock ( 0 ) {
qfa := queryFilterAction { }
switch d . Val ( ) {
case "replace" :
if ! d . NextArg ( ) {
return d . ArgErr ( )
}
qfa . Type = replaceAction
qfa . Parameter = d . Val ( )
if ! d . NextArg ( ) {
return d . ArgErr ( )
}
qfa . Value = d . Val ( )
2021-12-02 23:51:37 +03:00
case "hash" :
if ! d . NextArg ( ) {
return d . ArgErr ( )
}
qfa . Type = hashAction
qfa . Parameter = d . Val ( )
2021-11-23 12:01:43 +03:00
case "delete" :
if ! d . NextArg ( ) {
return d . ArgErr ( )
}
qfa . Type = deleteAction
qfa . Parameter = d . Val ( )
default :
return d . Errf ( "unrecognized subdirective %s" , d . Val ( ) )
}
m . Actions = append ( m . Actions , qfa )
}
}
return nil
}
// Filter filters the input field.
func ( m QueryFilter ) Filter ( in zapcore . Field ) zapcore . Field {
2023-08-30 01:59:43 +03:00
if array , ok := in . Interface . ( caddyhttp . LoggableStringArray ) ; ok {
newArray := make ( caddyhttp . LoggableStringArray , len ( array ) )
for i , s := range array {
newArray [ i ] = m . processQueryString ( s )
}
in . Interface = newArray
} else {
in . String = m . processQueryString ( in . String )
}
return in
}
func ( m QueryFilter ) processQueryString ( s string ) string {
u , err := url . Parse ( s )
2021-11-23 12:01:43 +03:00
if err != nil {
2023-08-30 01:59:43 +03:00
return s
2021-11-23 12:01:43 +03:00
}
q := u . Query ( )
for _ , a := range m . Actions {
switch a . Type {
case replaceAction :
for i := range q [ a . Parameter ] {
q [ a . Parameter ] [ i ] = a . Value
}
2021-12-02 23:51:37 +03:00
case hashAction :
for i := range q [ a . Parameter ] {
q [ a . Parameter ] [ i ] = hash ( a . Value )
}
2021-11-23 12:01:43 +03:00
case deleteAction :
q . Del ( a . Parameter )
}
}
u . RawQuery = q . Encode ( )
2023-08-30 01:59:43 +03:00
return u . String ( )
2021-11-23 12:01:43 +03:00
}
2021-11-23 19:40:20 +03:00
type cookieFilterAction struct {
2021-12-02 23:51:37 +03:00
// `replace` to replace the value of the cookie, `hash` to replace it with the 4 initial bytes of the SHA-256 of its content or `delete` to remove it entirely.
2021-11-23 19:40:20 +03:00
Type filterAction ` json:"type" `
// The name of the cookie.
Name string ` json:"name" `
// The value to use as replacement if the action is `replace`.
Value string ` json:"value,omitempty" `
}
// CookieFilter is a Caddy log field filter that filters
// cookies.
//
// This filter updates the logged HTTP header string
2021-12-02 23:51:37 +03:00
// to remove, replace or hash cookies containing sensitive data. For instance,
2021-11-23 19:40:20 +03:00
// it can be used to redact any kind of secrets, such as session IDs.
//
// If several actions are configured for the same cookie name, only the first
// will be applied.
type CookieFilter struct {
// A list of actions to apply to the cookies.
Actions [ ] cookieFilterAction ` json:"actions" `
}
// Validate checks that action types are correct.
func ( f * CookieFilter ) Validate ( ) error {
for _ , a := range f . Actions {
if err := a . Type . IsValid ( ) ; err != nil {
return err
}
}
return nil
}
// CaddyModule returns the Caddy module information.
func ( CookieFilter ) CaddyModule ( ) caddy . ModuleInfo {
return caddy . ModuleInfo {
ID : "caddy.logging.encoders.filter.cookie" ,
New : func ( ) caddy . Module { return new ( CookieFilter ) } ,
}
}
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func ( m * CookieFilter ) UnmarshalCaddyfile ( d * caddyfile . Dispenser ) error {
for d . Next ( ) {
for d . NextBlock ( 0 ) {
cfa := cookieFilterAction { }
switch d . Val ( ) {
case "replace" :
if ! d . NextArg ( ) {
return d . ArgErr ( )
}
cfa . Type = replaceAction
cfa . Name = d . Val ( )
if ! d . NextArg ( ) {
return d . ArgErr ( )
}
cfa . Value = d . Val ( )
2021-12-02 23:51:37 +03:00
case "hash" :
if ! d . NextArg ( ) {
return d . ArgErr ( )
}
cfa . Type = hashAction
cfa . Name = d . Val ( )
2021-11-23 19:40:20 +03:00
case "delete" :
if ! d . NextArg ( ) {
return d . ArgErr ( )
}
cfa . Type = deleteAction
cfa . Name = d . Val ( )
default :
return d . Errf ( "unrecognized subdirective %s" , d . Val ( ) )
}
m . Actions = append ( m . Actions , cfa )
}
}
return nil
}
// Filter filters the input field.
func ( m CookieFilter ) Filter ( in zapcore . Field ) zapcore . Field {
2022-08-09 04:11:02 +03:00
cookiesSlice , ok := in . Interface . ( caddyhttp . LoggableStringArray )
if ! ok {
return in
}
// using a dummy Request to make use of the Cookies() function to parse it
originRequest := http . Request { Header : http . Header { "Cookie" : cookiesSlice } }
2021-11-23 19:40:20 +03:00
cookies := originRequest . Cookies ( )
transformedRequest := http . Request { Header : make ( http . Header ) }
OUTER :
for _ , c := range cookies {
for _ , a := range m . Actions {
if c . Name != a . Name {
continue
}
switch a . Type {
case replaceAction :
c . Value = a . Value
transformedRequest . AddCookie ( c )
continue OUTER
2021-12-02 23:51:37 +03:00
case hashAction :
c . Value = hash ( c . Value )
transformedRequest . AddCookie ( c )
continue OUTER
2021-11-23 19:40:20 +03:00
case deleteAction :
continue OUTER
}
}
transformedRequest . AddCookie ( c )
}
2022-08-09 04:11:02 +03:00
in . Interface = caddyhttp . LoggableStringArray ( transformedRequest . Header [ "Cookie" ] )
2021-11-23 19:40:20 +03:00
return in
}
2021-11-23 20:00:20 +03:00
// RegexpFilter is a Caddy log field filter that
2022-10-05 08:21:23 +03:00
// replaces the field matching the provided regexp
// with the indicated string. If the field is an
// array of strings, each of them will have the
// regexp replacement applied.
2021-11-23 20:00:20 +03:00
type RegexpFilter struct {
// The regular expression pattern defining what to replace.
RawRegexp string ` json:"regexp,omitempty" `
// The value to use as replacement
Value string ` json:"value,omitempty" `
regexp * regexp . Regexp
}
// CaddyModule returns the Caddy module information.
func ( RegexpFilter ) CaddyModule ( ) caddy . ModuleInfo {
return caddy . ModuleInfo {
ID : "caddy.logging.encoders.filter.regexp" ,
New : func ( ) caddy . Module { return new ( RegexpFilter ) } ,
}
}
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func ( f * RegexpFilter ) UnmarshalCaddyfile ( d * caddyfile . Dispenser ) error {
for d . Next ( ) {
if d . NextArg ( ) {
f . RawRegexp = d . Val ( )
}
if d . NextArg ( ) {
f . Value = d . Val ( )
}
}
return nil
}
// Provision compiles m's regexp.
func ( m * RegexpFilter ) Provision ( ctx caddy . Context ) error {
r , err := regexp . Compile ( m . RawRegexp )
if err != nil {
return err
}
m . regexp = r
return nil
}
// Filter filters the input field with the replacement value if it matches the regexp.
func ( f * RegexpFilter ) Filter ( in zapcore . Field ) zapcore . Field {
2022-10-05 08:21:23 +03:00
if array , ok := in . Interface . ( caddyhttp . LoggableStringArray ) ; ok {
2023-08-29 20:41:39 +03:00
newArray := make ( caddyhttp . LoggableStringArray , len ( array ) )
2022-10-05 08:21:23 +03:00
for i , s := range array {
2023-08-29 20:41:39 +03:00
newArray [ i ] = f . regexp . ReplaceAllString ( s , f . Value )
2022-10-05 08:21:23 +03:00
}
2023-08-29 20:41:39 +03:00
in . Interface = newArray
2022-10-05 08:21:23 +03:00
} else {
in . String = f . regexp . ReplaceAllString ( in . String , f . Value )
}
2021-11-23 20:00:20 +03:00
return in
}
2022-04-28 18:38:44 +03:00
// RenameFilter is a Caddy log field filter that
// renames the field's key with the indicated name.
type RenameFilter struct {
Name string ` json:"name,omitempty" `
}
// CaddyModule returns the Caddy module information.
func ( RenameFilter ) CaddyModule ( ) caddy . ModuleInfo {
return caddy . ModuleInfo {
ID : "caddy.logging.encoders.filter.rename" ,
New : func ( ) caddy . Module { return new ( RenameFilter ) } ,
}
}
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func ( f * RenameFilter ) UnmarshalCaddyfile ( d * caddyfile . Dispenser ) error {
for d . Next ( ) {
if d . NextArg ( ) {
f . Name = d . Val ( )
}
}
return nil
}
// Filter renames the input field with the replacement name.
func ( f * RenameFilter ) Filter ( in zapcore . Field ) zapcore . Field {
in . Key = f . Name
return in
}
2020-09-15 21:37:41 +03:00
// Interface guards
var (
_ LogFieldFilter = ( * DeleteFilter ) ( nil )
2022-04-28 18:38:44 +03:00
_ LogFieldFilter = ( * HashFilter ) ( nil )
2021-07-12 18:13:01 +03:00
_ LogFieldFilter = ( * ReplaceFilter ) ( nil )
2020-09-15 21:37:41 +03:00
_ LogFieldFilter = ( * IPMaskFilter ) ( nil )
2021-11-23 12:01:43 +03:00
_ LogFieldFilter = ( * QueryFilter ) ( nil )
2021-11-23 19:40:20 +03:00
_ LogFieldFilter = ( * CookieFilter ) ( nil )
2021-11-23 20:00:20 +03:00
_ LogFieldFilter = ( * RegexpFilter ) ( nil )
2022-04-28 18:38:44 +03:00
_ LogFieldFilter = ( * RenameFilter ) ( nil )
2020-09-15 21:37:41 +03:00
_ caddyfile . Unmarshaler = ( * DeleteFilter ) ( nil )
2022-04-28 18:38:44 +03:00
_ caddyfile . Unmarshaler = ( * HashFilter ) ( nil )
2021-07-12 18:13:01 +03:00
_ caddyfile . Unmarshaler = ( * ReplaceFilter ) ( nil )
2020-09-15 21:37:41 +03:00
_ caddyfile . Unmarshaler = ( * IPMaskFilter ) ( nil )
2021-11-23 12:01:43 +03:00
_ caddyfile . Unmarshaler = ( * QueryFilter ) ( nil )
2021-11-23 19:40:20 +03:00
_ caddyfile . Unmarshaler = ( * CookieFilter ) ( nil )
2021-11-23 20:00:20 +03:00
_ caddyfile . Unmarshaler = ( * RegexpFilter ) ( nil )
2022-04-28 18:38:44 +03:00
_ caddyfile . Unmarshaler = ( * RenameFilter ) ( nil )
2020-09-15 21:37:41 +03:00
_ caddy . Provisioner = ( * IPMaskFilter ) ( nil )
2021-11-23 20:00:20 +03:00
_ caddy . Provisioner = ( * RegexpFilter ) ( nil )
2021-11-23 12:01:43 +03:00
_ caddy . Validator = ( * QueryFilter ) ( nil )
2020-09-15 21:37:41 +03:00
)