add stream mode

This commit is contained in:
Max Lerebourg
2022-09-28 13:39:34 +02:00
parent 9adb7d9500
commit 17ff76b108
+53 -9
View File
@@ -11,7 +11,6 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"regexp"
"text/template" "text/template"
"time" "time"
@@ -22,13 +21,13 @@ const (
realIpHeader = "X-Real-Ip" realIpHeader = "X-Real-Ip"
forwardHeader = "X-Forwarded-For" forwardHeader = "X-Forwarded-For"
crowdsecAuthHeader = "X-Api-Key" crowdsecAuthHeader = "X-Api-Key"
crowdsecBouncerRoute = "v1/decisions" crowdsecRoute = "v1/decisions"
crowdsecBouncerStreamRoute = "v1/decisions/stream" crowdsecStreamRoute = "v1/decisions/stream"
cacheBannedValue = "t" cacheBannedValue = "t"
cacheNoBannedValue = "f" cacheNoBannedValue = "f"
) )
var ipRegex = regexp.MustCompile(`\b\d+\.\d+\.\d+\.\d+\b`) // var ipRegex = regexp.MustCompile(`\b\d+\.\d+\.\d+\.\d+\b`)
var cache = ttl_map.New() var cache = ttl_map.New()
type Config struct { type Config struct {
@@ -44,7 +43,7 @@ type Config struct {
func CreateConfig() *Config { func CreateConfig() *Config {
return &Config{ return &Config{
Enabled: false, Enabled: false,
CrowdsecMode: "live", CrowdsecMode: "stream",
CrowdsecLapiScheme: "http", CrowdsecLapiScheme: "http",
CrowdsecLapiHost: "crowdsec:8080", CrowdsecLapiHost: "crowdsec:8080",
CrowdsecLapiKey: "", CrowdsecLapiKey: "",
@@ -59,6 +58,7 @@ type Bouncer struct {
template *template.Template template *template.Template
enabled bool enabled bool
crowdsecStreamHealthy bool
crowdsecScheme string crowdsecScheme string
crowdsecHost string crowdsecHost string
crowdsecKey string crowdsecKey string
@@ -90,19 +90,20 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
testUrl := url.URL{ testUrl := url.URL{
Scheme: config.CrowdsecLapiScheme, Scheme: config.CrowdsecLapiScheme,
Host: config.CrowdsecLapiHost, Host: config.CrowdsecLapiHost,
Path: crowdsecBouncerRoute, Path: crowdsecRoute,
} }
_, err := http.NewRequest(http.MethodGet, testUrl.String(), nil) _, err := http.NewRequest(http.MethodGet, testUrl.String(), nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("CrowdsecLapiScheme://CrowdsecLapiHost: '%v://%v' must be an URL", config.CrowdsecLapiScheme, config.CrowdsecLapiHost) return nil, fmt.Errorf("CrowdsecLapiScheme://CrowdsecLapiHost: '%v://%v' must be an URL", config.CrowdsecLapiScheme, config.CrowdsecLapiHost)
} }
return &Bouncer{ bouncer := &Bouncer{
next: next, next: next,
name: name, name: name,
template: template.New("CrowdsecBouncer").Delims("[[", "]]"), template: template.New("CrowdsecBouncer").Delims("[[", "]]"),
enabled: config.Enabled, enabled: config.Enabled,
crowdsecStreamHealthy: false,
crowdsecMode: config.CrowdsecMode, crowdsecMode: config.CrowdsecMode,
crowdsecScheme: config.CrowdsecLapiScheme, crowdsecScheme: config.CrowdsecLapiScheme,
crowdsecHost: config.CrowdsecLapiHost, crowdsecHost: config.CrowdsecLapiHost,
@@ -116,7 +117,9 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
}, },
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
}, },
}, nil }
go handleStreamCache(bouncer, "true")
return bouncer, nil
} }
func (a *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (a *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@@ -154,7 +157,7 @@ func (a *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
noneUrl := url.URL{ noneUrl := url.URL{
Scheme: a.crowdsecScheme, Scheme: a.crowdsecScheme,
Host: a.crowdsecHost, Host: a.crowdsecHost,
Path: crowdsecBouncerRoute, Path: crowdsecRoute,
RawQuery: fmt.Sprintf("ip=%v&banned=true", remoteHost), RawQuery: fmt.Sprintf("ip=%v&banned=true", remoteHost),
} }
req, _ := http.NewRequest(http.MethodGet, noneUrl.String(), nil) req, _ := http.NewRequest(http.MethodGet, noneUrl.String(), nil)
@@ -258,4 +261,45 @@ func setDecision(clientIP string, isBanned bool, duration int64) {
} else { } else {
cache.Set(clientIP, cacheNoBannedValue, duration) cache.Set(clientIP, cacheNoBannedValue, duration)
} }
}
func handleStreamCache(a *Bouncer, initialized string) {
time.AfterFunc(time.Duration(a.updateInterval) * time.Second, func () {
handleStreamCache(a, "false")
})
streamUrl := url.URL{
Scheme: a.crowdsecScheme,
Host: a.crowdsecHost,
Path: crowdsecStreamRoute,
RawQuery: fmt.Sprintf("startup=%s", initialized),
}
req, _ := http.NewRequest(http.MethodGet, streamUrl.String(), nil)
req.Header.Add(crowdsecAuthHeader, a.crowdsecKey)
res, err := a.client.Do(req)
if err != nil || res.StatusCode == http.StatusForbidden {
a.crowdsecStreamHealthy = false
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
a.crowdsecStreamHealthy = false
return
}
var stream Stream
err = json.Unmarshal(body, &stream)
if err != nil {
a.crowdsecStreamHealthy = false
return
}
for _, decision := range stream.New {
duration, err := time.ParseDuration(decision.Duration)
if err == nil {
setDecision(decision.Value, true, int64(duration.Seconds()))
}
}
for _, decision := range stream.Deleted {
cache.Del(decision.Value)
}
a.crowdsecStreamHealthy = true
} }