Skip to content

Commit

Permalink
Merge pull request #2 from juitde/logging
Browse files Browse the repository at this point in the history
Add logging to plugin
  • Loading branch information
dkreuer authored Jan 14, 2024
2 parents 7c91021 + bb85a91 commit f4ee19d
Show file tree
Hide file tree
Showing 16 changed files with 732 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .traefik.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type: middleware

import: github.com/juitde/traefik-plugin-fail2ban

summary: 'Block or allow IPs depending on various conditions'
summary: 'Block or allow IPs depending on various conditions (requires Traefik >= 2.10.0)'

testData:
enabled: true
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ Inspirations taken from:

Installation instructions are provided via the [traefik Plugin Catalog](https://plugins.traefik.io/plugins/).

### CAUTION: Breaking Changes

#### Version 0.2.0

- traefik v2.10+ is required due to now having a vendored dependency which results
in go routine panics in previous traefik versions.

## Configuration

All configuration options may be specified either in config files or as CLI parameters.
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.8'

services:
traefik:
image: traefik:v2.9
image: traefik:v2.10
ports:
- target: 8080
published: 8080
Expand Down
16 changes: 15 additions & 1 deletion fail2ban.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"strconv"
"strings"
"time"

"github.com/zerodha/logf"
)

type configIPSpec struct {
Expand Down Expand Up @@ -64,6 +66,7 @@ type Fail2Ban struct {
next http.Handler
name string
cache *Cache
logger *logf.Logger
enabled bool
staticAllowedIPNets []*net.IPNet
staticDeniedIPNets []*net.IPNet
Expand Down Expand Up @@ -147,11 +150,12 @@ func parseResponseRules(config configResponse) responseRules {
}

// New creates a Fail2Ban plugin instance.
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
func New(_ context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
return &Fail2Ban{
next: next,
name: name,
cache: NewCache(),
logger: NewLogger(config.LogLevel),
enabled: config.Enabled,
staticAllowedIPNets: parseConfigIPList(config.AlwaysAllowed.IP),
staticDeniedIPNets: parseConfigIPList(config.AlwaysDenied.IP),
Expand All @@ -164,25 +168,32 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h

func (a *Fail2Ban) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
if !a.enabled {
a.logger.Debug("Handler is not enabled. Skipping.", "phase", "accept_request")
a.next.ServeHTTP(responseWriter, request)
return
}
a.logger.Debug("Handler is enabled. Analyzing request.", "phase", "accept_request")

remoteIP, _, err := net.SplitHostPort(request.RemoteAddr)
if err != nil {
a.logger.Error("Failed to detect remoteIP from request.RemoteAddr.", "request.RemoteAddr", request.RemoteAddr, "phase", "accept_request")
responseWriter.WriteHeader(http.StatusInternalServerError)
return
}

for _, ipNet := range a.staticDeniedIPNets {
a.logger.Debug("Checking if remoteIP is in static denied Netmask.", "remoteIP", remoteIP, "netmask", ipNet, "phase", "check_request")
if ipNet.Contains(net.ParseIP(remoteIP)) {
a.logger.Info("RemoteIP was found in staticDeniedIPNets. Access Denied.", "remoteIP", remoteIP, "staticDeniedIPNets", a.staticDeniedIPNets, "phase", "check_request", "status", "denied")
responseWriter.WriteHeader(http.StatusForbidden)
return
}
}

for _, ipNet := range a.staticAllowedIPNets {
a.logger.Debug("Checking if remoteIP is in static allowed Netmask.", "remoteIP", remoteIP, "netmask", ipNet, "phase", "check_request")
if ipNet.Contains(net.ParseIP(remoteIP)) {
a.logger.Info("RemoteIP was found in staticAllowedIPNets. Access Granted.", "remoteIP", remoteIP, "staticAllowedIPNets", a.staticAllowedIPNets, "phase", "check_request", "status", "granted")
a.next.ServeHTTP(responseWriter, request)
return
}
Expand All @@ -194,10 +205,12 @@ func (a *Fail2Ban) ServeHTTP(responseWriter http.ResponseWriter, request *http.R
entry := a.cache.CreateEntry(remoteIP, requestTime)

if entry.GetTimesSeen() >= a.maxRetries && !entry.IsBanned() {
a.logger.Info("Client has been banned.", "remoteIP", remoteIP, "maxRetries", a.maxRetries, "banTime", a.banTime, "findTime", a.findTime, "phase", "check_request", "status", "denied")
entry.IssueBan()
}

if entry.IsBanned() {
a.logger.Debug("Client is still banned.", "remoteIP", remoteIP, "phase", "check_request", "status", "denied")
responseWriter.WriteHeader(http.StatusForbidden)
return
}
Expand All @@ -208,6 +221,7 @@ func (a *Fail2Ban) ServeHTTP(responseWriter http.ResponseWriter, request *http.R
// Response rules will be checked in the wrapped response writer
wrappedResponseWriter := &ResponseWriter{
ResponseWriter: responseWriter,
logger: a.logger,
cacheEntry: entry,
rules: a.responseRules,
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/juitde/traefik-plugin-fail2ban

go 1.19

require github.com/zerodha/logf v0.5.5
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/zerodha/logf v0.5.5 h1:AhxHlixHNYwhFjvlgTv6uO4VBKYKxx2I6SbHoHtWLBk=
github.com/zerodha/logf v0.5.5/go.mod h1:HWpfKsie+WFFpnUnUxelT6Z0FC6xu9+qt+oXNMPg6y8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
28 changes: 28 additions & 0 deletions logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package traefik_plugin_fail2ban //nolint:revive,stylecheck

import (
"fmt"
"strings"
"time"

"github.com/zerodha/logf"
)

// NewLogger creates new instance of logger.
func NewLogger(logLevel string) *logf.Logger {
parsedLogLevel, err := logf.LevelFromString(strings.ToLower(logLevel))
if err != nil {
parsedLogLevel = logf.InfoLevel
}
logger := logf.New(logf.Opts{
EnableColor: false,
Level: parsedLogLevel,
EnableCaller: false,
TimestampFormat: fmt.Sprintf("\"%s\"", time.RFC3339),
DefaultFields: []any{"plugin", "JUIT Fail2Ban"},
})

logger.Debug(fmt.Sprintf("Setting log level to %s", strings.ToUpper(parsedLogLevel.String())), "phase", "initialize")

return &logger
}
6 changes: 6 additions & 0 deletions response_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package traefik_plugin_fail2ban //nolint:revive,stylecheck
import (
"net/http"
"strconv"

"github.com/zerodha/logf"
)

// ResponseWriter wrapping original ResponseWriter with response check handling.
type ResponseWriter struct {
http.ResponseWriter

logger *logf.Logger
cacheEntry *CacheEntry
rules responseRules
}
Expand All @@ -20,7 +23,10 @@ func (rw *ResponseWriter) WriteHeader(code int) {

if rw.rules.StatusCode != nil {
if rw.rules.StatusCode.MatchString(strconv.Itoa(code)) {
rw.logger.Debug("Response Status Code matched rule. Incrementing.", "statusCodeRegexp", rw.rules.StatusCode, "responseStatusCode", code, "phase", "check_response", "status", "denied")
rw.cacheEntry.IncrementTimesSeen()
} else {
rw.logger.Debug("Response Status Code does not match rule. Skipping.", "statusCodeRegexp", rw.rules.StatusCode, "responseStatusCode", code, "phase", "check_response", "status", "granted")
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions vendor/github.com/zerodha/logf/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions vendor/github.com/zerodha/logf/.goreleaser.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions vendor/github.com/zerodha/logf/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions vendor/github.com/zerodha/logf/Makefile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

102 changes: 102 additions & 0 deletions vendor/github.com/zerodha/logf/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f4ee19d

Please sign in to comment.