add one instance of cache for all the service (#19)

*  add one instance of cache for all the service

* 🐛 fix the healthy issue

* 🐛 fix lint

* 🍱 upgrade codebase

* 🍱 fix lint

* 🐛 when there is master node shut down

* Set live mode as default, clean docker and recipes

* Restore live mode in readme as default

* 🍱 add channel management

* 🍱 order funcs

* 🍱 revert service in docker compose

*  logLevel added + cache put out the bouncer

* 🍱 fix

* 🍱 fix

* 🍱 fix

Co-authored-by: MathieuHa <mathieu@hanotaux.fr>
This commit is contained in:
maxlerebourg
2022-10-16 20:14:41 +02:00
committed by GitHub
co-authored by MathieuHa
parent f22fc2cd09
commit 8696501f61
7 changed files with 191 additions and 102 deletions
+39
View File
@@ -0,0 +1,39 @@
package cache
import (
"fmt"
ttl_map "github.com/leprosus/golang-ttl-map"
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
)
const (
cacheBannedValue = "t"
cacheNoBannedValue = "f"
)
var cache = ttl_map.New()
// Get Decision check in the cache if the IP has the banned / not banned value.
// Otherwise return with an error to add the IP in cache if we are on.
func GetDecision(clientIP string) (bool, error) {
banned, isCached := cache.Get(clientIP)
bannedString, isValid := banned.(string)
if isCached && isValid && len(bannedString) > 0 {
return bannedString == cacheBannedValue, nil
}
return false, fmt.Errorf("no cache data")
}
func SetDecision(clientIP string, isBanned bool, duration int64) {
if isBanned {
logger.Debug(fmt.Sprintf("%v banned", clientIP))
cache.Set(clientIP, cacheBannedValue, duration)
} else {
cache.Set(clientIP, cacheNoBannedValue, duration)
}
}
func DeleteDecision(clientIP string) {
cache.Del(clientIP)
}
+127
View File
@@ -0,0 +1,127 @@
package ip
import (
"errors"
"fmt"
"net"
"net/http"
"strings"
)
// CHECKER
// Checker allows to check that addresses are in a trusted IPs.
type Checker struct {
authorizedIPs []*net.IP
authorizedIPsNet []*net.IPNet
}
// NewChecker builds a new Checker given a list of CIDR-Strings to trusted IPs.
func NewChecker(trustedIPs []string) (*Checker, error) {
if len(trustedIPs) == 0 {
return nil, errors.New("no trusted IPs provided")
}
checker := &Checker{}
for _, ipMask := range trustedIPs {
if ipAddr := net.ParseIP(ipMask); ipAddr != nil {
checker.authorizedIPs = append(checker.authorizedIPs, &ipAddr)
continue
}
_, ipAddr, err := net.ParseCIDR(ipMask)
if err != nil {
return nil, fmt.Errorf("parsing CIDR trusted IPs %s: %w", ipAddr, err)
}
checker.authorizedIPsNet = append(checker.authorizedIPsNet, ipAddr)
}
return checker, nil
}
// Contains checks if provided address is in the trusted IPs.
func (ip *Checker) Contains(addr string) (bool, error) {
if len(addr) == 0 {
return false, errors.New("empty IP address")
}
ipAddr, err := parseIP(addr)
if err != nil {
return false, fmt.Errorf("unable to parse address: %s: %w", addr, err)
}
return ip.ContainsIP(ipAddr), nil
}
// ContainsIP checks if provided address is in the trusted IPs.
func (ip *Checker) ContainsIP(addr net.IP) bool {
for _, authorizedIP := range ip.authorizedIPs {
if authorizedIP.Equal(addr) {
return true
}
}
for _, authorizedNet := range ip.authorizedIPsNet {
if authorizedNet.Contains(addr) {
return true
}
}
return false
}
func parseIP(addr string) (net.IP, error) {
userIP := net.ParseIP(addr)
if userIP == nil {
return nil, fmt.Errorf("can't parse IP from address %s", addr)
}
return userIP, nil
}
// STRATEGY
// PoolStrategy is a strategy based on an IP Checker.
// It allows to check whether addresses are in a given pool of IPs.
type PoolStrategy struct {
Checker *Checker
}
// GetIP checks the list of Forwarded IPs (most recent first) against the
// Checker pool of IPs. It returns the first IP that is not in the pool, or the
// empty string otherwise.
func (s *PoolStrategy) getIP(req *http.Request, customHeader string) string {
if s.Checker == nil {
return ""
}
xff := req.Header.Get(customHeader)
xffs := strings.Split(xff, ",")
for i := len(xffs) - 1; i >= 0; i-- {
xffTrimmed := strings.TrimSpace(xffs[i])
if len(xffTrimmed) == 0 {
continue
}
if contain, _ := s.Checker.Contains(xffTrimmed); !contain {
return xffTrimmed
}
}
return ""
}
// GetRemoteIP It returns the first IP that is not in the pool, or the empty string otherwise.
func GetRemoteIP(req *http.Request, strategy *PoolStrategy, customHeader string) (string, error) {
remoteIP := strategy.getIP(req, customHeader)
if len(remoteIP) != 0 {
return remoteIP, nil
}
remoteIP, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return "", fmt.Errorf("failed to extract ip from remote address: %w", err)
}
return remoteIP, nil
}
+35
View File
@@ -0,0 +1,35 @@
package logger
import (
"io"
"log"
"os"
)
var (
loggerInfo = log.New(io.Discard, "INFO: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime)
loggerDebug = log.New(io.Discard, "DEBUG: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime)
)
// Init Set Default log level to info in case log level to defined
func Init(logLevel string) {
switch logLevel {
case "INFO":
loggerInfo.SetOutput(os.Stdout)
case "DEBUG":
loggerInfo.SetOutput(os.Stdout)
loggerDebug.SetOutput(os.Stdout)
default:
loggerInfo.SetOutput(os.Stdout)
}
}
// Info Log info
func Info(str string) {
loggerInfo.Printf(str)
}
// Info Log debug
func Debug(str string) {
loggerDebug.Printf(str)
}