mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-07-21 11:38:59 +02:00
Update CI following latest plugindemo version (#34)
* Update CI following latest plugindemo version * disable some linter * Disable some linter * Fix linter in redis, add error logger * 🚨 Fix lint errors in private packages * 🚨 Update lints in package and core code * 🚨 exclude unfixable lint errors * 🚨 update lint at package level and ignore necessary global variable * 🍱 fix lint qnd merge * 🚨 Fix Lint for string * 🚨 Fix lint for bouncer * 🚨 Fix error * Fix weird linter error Co-authored-by: Max Lerebourg <maxlerebourg@gmail.com>
This commit is contained in:
co-authored by
Max Lerebourg
parent
2d0bea8eec
commit
0a54f7b09f
@@ -11,7 +11,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [ 1.17, 1.x ]
|
go-version: [ 1.18, 1.x ]
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ jobs:
|
|||||||
name: Main Process
|
name: Main Process
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
GO_VERSION: 1.17
|
GO_VERSION: 1.18
|
||||||
GOLANGCI_LINT_VERSION: v1.46.2
|
GOLANGCI_LINT_VERSION: v1.50.0
|
||||||
YAEGI_VERSION: v0.13.0
|
YAEGI_VERSION: v0.14.2
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
|
|||||||
+14
-2
@@ -29,11 +29,20 @@ linters-settings:
|
|||||||
linters:
|
linters:
|
||||||
enable-all: true
|
enable-all: true
|
||||||
disable:
|
disable:
|
||||||
|
- deadcode # deprecated
|
||||||
|
- exhaustivestruct # deprecated
|
||||||
|
- golint # deprecated
|
||||||
|
- ifshort # deprecated
|
||||||
- interfacer # deprecated
|
- interfacer # deprecated
|
||||||
- maligned # deprecated
|
- maligned # deprecated
|
||||||
|
- nosnakecase # deprecated
|
||||||
- scopelint # deprecated
|
- scopelint # deprecated
|
||||||
- golint # deprecated
|
- scopelint # deprecated
|
||||||
- exhaustivestruct # deprecated
|
- structcheck # deprecated
|
||||||
|
- varcheck # deprecated
|
||||||
|
- sqlclosecheck # not relevant (SQL)
|
||||||
|
- rowserrcheck # not relevant (SQL)
|
||||||
|
- execinquery # not relevant (SQL)
|
||||||
- cyclop # duplicate of gocyclo
|
- cyclop # duplicate of gocyclo
|
||||||
- bodyclose # Too many false positives: https://github.com/timakin/bodyclose/issues/30
|
- bodyclose # Too many false positives: https://github.com/timakin/bodyclose/issues/30
|
||||||
- dupl
|
- dupl
|
||||||
@@ -52,6 +61,9 @@ linters:
|
|||||||
- gomnd
|
- gomnd
|
||||||
- forbidigo
|
- forbidigo
|
||||||
- varnamelen
|
- varnamelen
|
||||||
|
- wastedassign # is disabled because of generics
|
||||||
|
- gofumpt
|
||||||
|
- gci
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
|
|||||||
+28
-22
@@ -1,4 +1,6 @@
|
|||||||
package crowdsec_bouncer_traefik_plugin
|
// Package crowdsec_bouncer_traefik_plugin implements a middleware that communicates with crowdsec.
|
||||||
|
// It can cache results to filesystem or redis, or even ask crowdsec for every requests.
|
||||||
|
package crowdsec_bouncer_traefik_plugin //nolint:revive,stylecheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -15,7 +17,7 @@ import (
|
|||||||
cache "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache"
|
cache "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache"
|
||||||
ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip"
|
ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip"
|
||||||
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
||||||
simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/redis"
|
simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/simpleredis"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -28,6 +30,7 @@ const (
|
|||||||
cacheTimeoutKey = "updated"
|
cacheTimeoutKey = "updated"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:gochecknoglobals
|
||||||
var (
|
var (
|
||||||
crowdsecStreamHealthy = false
|
crowdsecStreamHealthy = false
|
||||||
ticker chan bool
|
ticker chan bool
|
||||||
@@ -61,8 +64,8 @@ func CreateConfig() *Config {
|
|||||||
CrowdsecLapiKey: "",
|
CrowdsecLapiKey: "",
|
||||||
UpdateIntervalSeconds: 60,
|
UpdateIntervalSeconds: 60,
|
||||||
DefaultDecisionSeconds: 60,
|
DefaultDecisionSeconds: 60,
|
||||||
ForwardedHeadersTrustedIPs: []string{},
|
|
||||||
ClientTrustedIPs: []string{},
|
ClientTrustedIPs: []string{},
|
||||||
|
ForwardedHeadersTrustedIPs: []string{},
|
||||||
ForwardedHeadersCustomName: "X-Forwarded-For",
|
ForwardedHeadersCustomName: "X-Forwarded-For",
|
||||||
RedisCacheEnabled: false,
|
RedisCacheEnabled: false,
|
||||||
RedisCacheHost: "redis:6379",
|
RedisCacheHost: "redis:6379",
|
||||||
@@ -83,8 +86,8 @@ type Bouncer struct {
|
|||||||
updateInterval int64
|
updateInterval int64
|
||||||
defaultDecisionTimeout int64
|
defaultDecisionTimeout int64
|
||||||
customHeader string
|
customHeader string
|
||||||
serverPoolStrategy *ip.PoolStrategy
|
|
||||||
clientPoolStrategy *ip.PoolStrategy
|
clientPoolStrategy *ip.PoolStrategy
|
||||||
|
serverPoolStrategy *ip.PoolStrategy
|
||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,41 +144,44 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP principal function of plugin.
|
// ServeHTTP principal function of plugin.
|
||||||
|
//
|
||||||
|
//nolint:nestif
|
||||||
func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
if !bouncer.enabled {
|
if !bouncer.enabled {
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
bouncer.next.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we check for the trusted IPs in the customHeader
|
// Here we check for the trusted IPs in the customHeader
|
||||||
remoteHost, err := ip.GetRemoteIP(req, bouncer.serverPoolStrategy, bouncer.customHeader)
|
remoteIP, err := ip.GetRemoteIP(req, bouncer.serverPoolStrategy, bouncer.customHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Info(err.Error())
|
logger.Error(fmt.Sprintf("ServeHTTP ip:%s %s", remoteIP, err.Error()))
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
trusted, err := bouncer.clientPoolStrategy.Checker.Contains(remoteIP)
|
||||||
trusted, err := bouncer.clientPoolStrategy.Checker.Contains(remoteHost)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Info(err.Error())
|
logger.Info(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if our IP is in the trusted list we bypass the next checks
|
// if our IP is in the trusted list we bypass the next checks
|
||||||
logger.Debug(fmt.Sprintf("ServeHTTP ip:%v isTrusted:%v", remoteHost, trusted))
|
logger.Debug(fmt.Sprintf("ServeHTTP ip:%s isTrusted:%v", remoteIP, trusted))
|
||||||
if trusted {
|
if trusted {
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
bouncer.next.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO This should be simplified
|
||||||
healthy := crowdsecStreamHealthy
|
healthy := crowdsecStreamHealthy
|
||||||
if bouncer.crowdsecMode != noneMode {
|
if bouncer.crowdsecMode != noneMode {
|
||||||
isBanned, err := cache.GetDecision(remoteHost)
|
isBanned, err := cache.GetDecision(remoteIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug(err.Error())
|
logger.Error(err.Error())
|
||||||
if err.Error() == simpleredis.RedisUnreachable {
|
if err.Error() == simpleredis.RedisUnreachable {
|
||||||
healthy = false
|
healthy = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.Debug(fmt.Sprintf("ServeHTTP cache:hit isBanned:%v", isBanned))
|
logger.Debug(fmt.Sprintf("ServeHTTP ip:%s cache:hit isBanned:%v", remoteIP, isBanned))
|
||||||
if isBanned {
|
if isBanned {
|
||||||
rw.WriteHeader(http.StatusForbidden)
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
} else {
|
} else {
|
||||||
@@ -193,7 +199,7 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
rw.WriteHeader(http.StatusForbidden)
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handleNoStreamCache(bouncer, rw, req, remoteHost)
|
handleNoStreamCache(bouncer, rw, req, remoteIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,12 +251,12 @@ func startTicker(config *Config, work func()) chan bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We are now in none or live mode.
|
// We are now in none or live mode.
|
||||||
func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Request, remoteHost string) {
|
func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Request, remoteIP string) {
|
||||||
routeURL := url.URL{
|
routeURL := url.URL{
|
||||||
Scheme: bouncer.crowdsecScheme,
|
Scheme: bouncer.crowdsecScheme,
|
||||||
Host: bouncer.crowdsecHost,
|
Host: bouncer.crowdsecHost,
|
||||||
Path: crowdsecLapiRoute,
|
Path: crowdsecLapiRoute,
|
||||||
RawQuery: fmt.Sprintf("ip=%v&banned=true", remoteHost),
|
RawQuery: fmt.Sprintf("ip=%v&banned=true", remoteIP),
|
||||||
}
|
}
|
||||||
body, err := crowdsecQuery(bouncer, routeURL.String())
|
body, err := crowdsecQuery(bouncer, routeURL.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -261,7 +267,7 @@ func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Req
|
|||||||
|
|
||||||
if bytes.Equal(body, []byte("null")) {
|
if bytes.Equal(body, []byte("null")) {
|
||||||
if bouncer.crowdsecMode == liveMode {
|
if bouncer.crowdsecMode == liveMode {
|
||||||
cache.SetDecision(remoteHost, false, bouncer.defaultDecisionTimeout)
|
cache.SetDecision(remoteIP, false, bouncer.defaultDecisionTimeout)
|
||||||
}
|
}
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
bouncer.next.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
@@ -276,7 +282,7 @@ func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Req
|
|||||||
}
|
}
|
||||||
if len(decisions) == 0 {
|
if len(decisions) == 0 {
|
||||||
if bouncer.crowdsecMode == liveMode {
|
if bouncer.crowdsecMode == liveMode {
|
||||||
cache.SetDecision(remoteHost, false, bouncer.defaultDecisionTimeout)
|
cache.SetDecision(remoteIP, false, bouncer.defaultDecisionTimeout)
|
||||||
}
|
}
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
bouncer.next.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
@@ -288,7 +294,7 @@ func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bouncer.crowdsecMode == liveMode {
|
if bouncer.crowdsecMode == liveMode {
|
||||||
cache.SetDecision(remoteHost, true, int64(duration.Seconds()))
|
cache.SetDecision(remoteIP, true, int64(duration.Seconds()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +346,7 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string) ([]byte, error) {
|
|||||||
req.Header.Add(crowdsecLapiHeader, bouncer.crowdsecKey)
|
req.Header.Add(crowdsecLapiHeader, bouncer.crowdsecKey)
|
||||||
res, err := bouncer.client.Do(req)
|
res, err := bouncer.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error while fetching %v: %s", stringURL, err)
|
return nil, fmt.Errorf("error while fetching %v: %w", stringURL, err)
|
||||||
}
|
}
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("error while fetching %v, status code: %d", stringURL, res.StatusCode)
|
return nil, fmt.Errorf("error while fetching %v, status code: %d", stringURL, res.StatusCode)
|
||||||
@@ -348,12 +354,12 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string) ([]byte, error) {
|
|||||||
defer func(body io.ReadCloser) {
|
defer func(body io.ReadCloser) {
|
||||||
err = body.Close()
|
err = body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Info(fmt.Sprintf("failed to close body reader: %s", err))
|
logger.Error(fmt.Sprintf("failed to close body reader: %s", err.Error()))
|
||||||
}
|
}
|
||||||
}(res.Body)
|
}(res.Body)
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error while reading body: %s", err)
|
return nil, fmt.Errorf("error while reading body: %w", err)
|
||||||
}
|
}
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package crowdsec_bouncer_traefik_plugin
|
package crowdsec_bouncer_traefik_plugin //nolint:revive,stylecheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
Vendored
+23
-14
@@ -1,3 +1,5 @@
|
|||||||
|
// Package cache implements utility routines for manipulating cache.
|
||||||
|
// It supports currently local file and redis cache.
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -6,7 +8,7 @@ import (
|
|||||||
ttl_map "github.com/leprosus/golang-ttl-map"
|
ttl_map "github.com/leprosus/golang-ttl-map"
|
||||||
|
|
||||||
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
||||||
simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/redis"
|
simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/simpleredis"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -14,12 +16,14 @@ const (
|
|||||||
cacheNoBannedValue = "f"
|
cacheNoBannedValue = "f"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cache = ttl_map.New()
|
//nolint:gochecknoglobals
|
||||||
var redis simpleredis.SimpleRedis
|
var (
|
||||||
|
cache = ttl_map.New()
|
||||||
|
redis simpleredis.SimpleRedis
|
||||||
|
redisEnabled = false
|
||||||
|
)
|
||||||
|
|
||||||
var redisEnabled = false
|
// FileSystem Cache
|
||||||
|
|
||||||
// CLASSIC
|
|
||||||
|
|
||||||
func getDecisionLocalCache(clientIP string) (bool, error) {
|
func getDecisionLocalCache(clientIP string) (bool, error) {
|
||||||
banned, isCached := cache.Get(clientIP)
|
banned, isCached := cache.Get(clientIP)
|
||||||
@@ -38,7 +42,7 @@ func deleteDecisionLocalCache(clientIP string) {
|
|||||||
cache.Del(clientIP)
|
cache.Del(clientIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// REDIS
|
// Redis Cache
|
||||||
|
|
||||||
func getDecisionRedisCache(clientIP string) (bool, error) {
|
func getDecisionRedisCache(clientIP string) (bool, error) {
|
||||||
banned, err := redis.Get(clientIP)
|
banned, err := redis.Get(clientIP)
|
||||||
@@ -50,14 +54,18 @@ func getDecisionRedisCache(clientIP string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setDecisionRedisCache(clientIP string, value string, duration int64) {
|
func setDecisionRedisCache(clientIP string, value string, duration int64) {
|
||||||
redis.Set(clientIP, []byte(value), duration)
|
if err := redis.Set(clientIP, []byte(value), duration); err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("cache:setDecisionRedisCache %s", err.Error()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteDecisionRedisCache(clientIP string) {
|
func deleteDecisionRedisCache(clientIP string) {
|
||||||
redis.Del(clientIP)
|
if err := redis.Del(clientIP); err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("cache:deleteDecisionRedisCache %s", err.Error()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDecision delete decision in cache
|
// DeleteDecision delete decision in cache.
|
||||||
func DeleteDecision(clientIP string) {
|
func DeleteDecision(clientIP string) {
|
||||||
if redisEnabled {
|
if redisEnabled {
|
||||||
deleteDecisionRedisCache(clientIP)
|
deleteDecisionRedisCache(clientIP)
|
||||||
@@ -71,15 +79,15 @@ func DeleteDecision(clientIP string) {
|
|||||||
func GetDecision(clientIP string) (bool, error) {
|
func GetDecision(clientIP string) (bool, error) {
|
||||||
if redisEnabled {
|
if redisEnabled {
|
||||||
return getDecisionRedisCache(clientIP)
|
return getDecisionRedisCache(clientIP)
|
||||||
} else {
|
|
||||||
return getDecisionLocalCache(clientIP)
|
|
||||||
}
|
}
|
||||||
|
return getDecisionLocalCache(clientIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDecision update the cache with the IP as key and the value banned / not banned.
|
||||||
func SetDecision(clientIP string, isBanned bool, duration int64) {
|
func SetDecision(clientIP string, isBanned bool, duration int64) {
|
||||||
var value string
|
var value string
|
||||||
if isBanned {
|
if isBanned {
|
||||||
logger.Debug(fmt.Sprintf("%v banned", clientIP))
|
logger.Debug(fmt.Sprintf("cache:SetDecision ip:%v banned", clientIP))
|
||||||
value = cacheBannedValue
|
value = cacheBannedValue
|
||||||
} else {
|
} else {
|
||||||
value = cacheNoBannedValue
|
value = cacheNoBannedValue
|
||||||
@@ -91,8 +99,9 @@ func SetDecision(clientIP string, isBanned bool, duration int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitRedisClient loads variables.
|
||||||
func InitRedisClient(host string) {
|
func InitRedisClient(host string) {
|
||||||
redisEnabled = true
|
redisEnabled = true
|
||||||
redis.Init(host)
|
redis.Init(host)
|
||||||
logger.Debug("Redis initialized")
|
logger.Debug("cache:InitRedisClient redis:initialized")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Package ip implements utility routines for to manipulates IP and CIDR.
|
||||||
|
// It allows to find on IP on a list, and find if an IP is part of a list of CIDR.
|
||||||
package ip
|
package ip
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
+15
-11
@@ -1,3 +1,5 @@
|
|||||||
|
// Package logger implements utility routines to write to stdout and stderr.
|
||||||
|
// It supports debug, info and error level
|
||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -7,29 +9,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
loggerInfo = log.New(io.Discard, "INFO: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime)
|
loggerInfo = log.New(io.Discard, "INFO: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime) //nolint:gochecknoglobals
|
||||||
loggerDebug = log.New(io.Discard, "DEBUG: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime)
|
loggerDebug = log.New(io.Discard, "DEBUG: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime) //nolint:gochecknoglobals
|
||||||
|
loggerError = log.New(io.Discard, "ERROR: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime) //nolint:gochecknoglobals
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init Set Default log level to info in case log level to defined
|
// Init Set Default log level to info in case log level to defined.
|
||||||
func Init(logLevel string) {
|
func Init(logLevel string) {
|
||||||
switch logLevel {
|
loggerError.SetOutput(os.Stderr)
|
||||||
case "INFO":
|
|
||||||
loggerInfo.SetOutput(os.Stdout)
|
|
||||||
case "DEBUG":
|
|
||||||
loggerInfo.SetOutput(os.Stdout)
|
loggerInfo.SetOutput(os.Stdout)
|
||||||
|
if logLevel == "DEBUG" {
|
||||||
loggerDebug.SetOutput(os.Stdout)
|
loggerDebug.SetOutput(os.Stdout)
|
||||||
default:
|
|
||||||
loggerInfo.SetOutput(os.Stdout)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info Log info
|
// Info log to Stdout.
|
||||||
func Info(str string) {
|
func Info(str string) {
|
||||||
loggerInfo.Printf(str)
|
loggerInfo.Printf(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info Log debug
|
// Debug log to Stdout.
|
||||||
func Debug(str string) {
|
func Debug(str string) {
|
||||||
loggerDebug.Printf(str)
|
loggerDebug.Printf(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error log to Stderr.
|
||||||
|
func Error(str string) {
|
||||||
|
loggerError.Printf(str)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Package simpleredis implements utility routines for interacting.
|
||||||
|
// It supports currently the following operations: GET, SET, DELETE,
|
||||||
|
// and support timetoleave for keys.
|
||||||
package simpleredis
|
package simpleredis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -11,12 +14,14 @@ import (
|
|||||||
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Error strings for redis.
|
||||||
const (
|
const (
|
||||||
RedisUnreachable = "redis:unreachable"
|
RedisUnreachable = "redis:unreachable"
|
||||||
RedisMiss = "redis:miss"
|
RedisMiss = "redis:miss"
|
||||||
RedisTimeout = "redis:timeout"
|
RedisTimeout = "redis:timeout"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A RedisCmd is used to communicate with redis at low level using commands.
|
||||||
type RedisCmd struct {
|
type RedisCmd struct {
|
||||||
Command string
|
Command string
|
||||||
Name string
|
Name string
|
||||||
@@ -25,6 +30,7 @@ type RedisCmd struct {
|
|||||||
Error error
|
Error error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A SimpleRedis is used to communicate with redis.
|
||||||
type SimpleRedis struct {
|
type SimpleRedis struct {
|
||||||
redisHost string
|
redisHost string
|
||||||
}
|
}
|
||||||
@@ -39,6 +45,14 @@ func genRedisArray(params ...[]byte) []byte {
|
|||||||
return []byte(MSG)
|
return []byte(MSG)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func send(wr *textproto.Writer, method string, data []byte) {
|
||||||
|
if err := wr.PrintfLine(string(data)); err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("redis:%s %s", method, err.Error()))
|
||||||
|
} else {
|
||||||
|
logger.Debug(fmt.Sprintf("redis:%s", method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
|
func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
|
||||||
dialer := net.Dialer{Timeout: 2 * time.Second}
|
dialer := net.Dialer{Timeout: 2 * time.Second}
|
||||||
conn, err := dialer.Dial("tcp", hostnamePort)
|
conn, err := dialer.Dial("tcp", hostnamePort)
|
||||||
@@ -46,24 +60,25 @@ func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
|
|||||||
channel <- RedisCmd{Error: fmt.Errorf(RedisUnreachable)}
|
channel <- RedisCmd{Error: fmt.Errorf(RedisUnreachable)}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer func() {
|
||||||
|
if err := conn.Close(); err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("redis:connClose %s", err.Error()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
writer := textproto.NewWriter(bufio.NewWriter(conn))
|
writer := textproto.NewWriter(bufio.NewWriter(conn))
|
||||||
reader := textproto.NewReader(bufio.NewReader(conn))
|
reader := textproto.NewReader(bufio.NewReader(conn))
|
||||||
|
|
||||||
switch cmd.Command {
|
switch cmd.Command {
|
||||||
case "SET":
|
case "SET":
|
||||||
data := genRedisArray([]byte("SET"), []byte(cmd.Name), []byte(cmd.Data), []byte("EX"), []byte(fmt.Sprintf("%v", cmd.Duration)))
|
data := genRedisArray([]byte("SET"), []byte(cmd.Name), cmd.Data, []byte("EX"), []byte(fmt.Sprintf("%d", cmd.Duration)))
|
||||||
writer.PrintfLine(string(data))
|
send(writer, "set", data)
|
||||||
logger.Debug("redis:set")
|
|
||||||
case "DEL":
|
case "DEL":
|
||||||
data := genRedisArray([]byte("DEL"), []byte(cmd.Name))
|
data := genRedisArray([]byte("DEL"), []byte(cmd.Name))
|
||||||
writer.PrintfLine(string(data))
|
send(writer, "del", data)
|
||||||
logger.Debug("redis:del")
|
|
||||||
case "GET":
|
case "GET":
|
||||||
data := genRedisArray([]byte("GET"), []byte(cmd.Name))
|
data := genRedisArray([]byte("GET"), []byte(cmd.Name))
|
||||||
writer.PrintfLine(string(data))
|
send(writer, "get", data)
|
||||||
logger.Debug("redis:get")
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Second * 1):
|
case <-time.After(time.Second * 1):
|
||||||
@@ -83,10 +98,12 @@ func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init sets the redisHost used to connect to redis.
|
||||||
func (sr *SimpleRedis) Init(redisHost string) {
|
func (sr *SimpleRedis) Init(redisHost string) {
|
||||||
sr.redisHost = redisHost
|
sr.redisHost = redisHost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get fetches the value for key name in redis.
|
||||||
func (sr *SimpleRedis) Get(name string) ([]byte, error) {
|
func (sr *SimpleRedis) Get(name string) ([]byte, error) {
|
||||||
redisCmd := RedisCmd{
|
redisCmd := RedisCmd{
|
||||||
Command: "GET",
|
Command: "GET",
|
||||||
@@ -101,6 +118,7 @@ func (sr *SimpleRedis) Get(name string) ([]byte, error) {
|
|||||||
return resp.Data, nil
|
return resp.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set update the value for key name in redis with value data for duration.
|
||||||
func (sr *SimpleRedis) Set(name string, data []byte, duration int64) error {
|
func (sr *SimpleRedis) Set(name string, data []byte, duration int64) error {
|
||||||
redisCmd := RedisCmd{
|
redisCmd := RedisCmd{
|
||||||
Command: "SET",
|
Command: "SET",
|
||||||
@@ -112,6 +130,7 @@ func (sr *SimpleRedis) Set(name string, data []byte, duration int64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Del remove the key name in redis.
|
||||||
func (sr *SimpleRedis) Del(name string) error {
|
func (sr *SimpleRedis) Del(name string) error {
|
||||||
redisCmd := RedisCmd{
|
redisCmd := RedisCmd{
|
||||||
Command: "DEL",
|
Command: "DEL",
|
||||||
Reference in New Issue
Block a user