mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-07-21 11:38:59 +02:00
@@ -1,2 +1,6 @@
|
|||||||
.idea/
|
.idea/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
config
|
||||||
|
db
|
||||||
|
logs
|
||||||
|
docker-compose.dev.yml
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
filenames:
|
filenames:
|
||||||
- /var/log/traefik/*.log
|
- /var/log/traefik/access.log
|
||||||
labels:
|
labels:
|
||||||
type: traefik
|
type: traefik
|
||||||
+26
-22
@@ -116,14 +116,17 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
|
|||||||
},
|
},
|
||||||
cache: ttl_map.New(),
|
cache: ttl_map.New(),
|
||||||
}
|
}
|
||||||
// if we are on a stream mode, we fetch in a go routine every minute the new decisions.
|
if config.CrowdsecMode == streamMode || config.CrowdsecMode == aloneMode {
|
||||||
if config.CrowdsecMode == streamMode {
|
if config.CrowdsecMode == aloneMode {
|
||||||
go handleStreamCache(bouncer, true)
|
|
||||||
} else if config.CrowdsecMode == aloneMode {
|
|
||||||
getToken(bouncer)
|
getToken(bouncer)
|
||||||
time.AfterFunc(10*time.Second, func() {
|
}
|
||||||
handleStreamCache(bouncer, false)
|
ticker := time.NewTicker(time.Duration(config.UpdateIntervalSeconds) * time.Second)
|
||||||
})
|
go func() {
|
||||||
|
go handleStreamCache(bouncer)
|
||||||
|
for range ticker.C {
|
||||||
|
go handleStreamCache(bouncer)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
return bouncer, nil
|
return bouncer, nil
|
||||||
}
|
}
|
||||||
@@ -138,7 +141,7 @@ func (a *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
// TODO Make sur remote address does not include the port.
|
// TODO Make sur remote address does not include the port.
|
||||||
remoteHost, _, err := net.SplitHostPort(req.RemoteAddr)
|
remoteHost, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to extract ip from remote address: %v", err)
|
logger(fmt.Sprintf("failed to extract ip from remote address: %v", err))
|
||||||
a.next.ServeHTTP(rw, req)
|
a.next.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -195,6 +198,10 @@ type Login struct {
|
|||||||
Expire string `json:"expire"`
|
Expire string `json:"expire"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logger(str string) {
|
||||||
|
log.Printf("Crowdsec Bouncer Traefik Plugin - %s", str)
|
||||||
|
}
|
||||||
|
|
||||||
func contains(source []string, target string) bool {
|
func contains(source []string, target string) bool {
|
||||||
for _, a := range source {
|
for _, a := range source {
|
||||||
if a == target {
|
if a == target {
|
||||||
@@ -217,7 +224,7 @@ func getDecision(cache *ttl_map.Heap, clientIP string) (bool, error) {
|
|||||||
|
|
||||||
func setDecision(cache *ttl_map.Heap, clientIP string, isBanned bool, duration int64) {
|
func setDecision(cache *ttl_map.Heap, clientIP string, isBanned bool, duration int64) {
|
||||||
if isBanned {
|
if isBanned {
|
||||||
log.Printf("%v banned", clientIP)
|
logger(fmt.Sprintf("%v banned", clientIP))
|
||||||
cache.Set(clientIP, cacheBannedValue, duration)
|
cache.Set(clientIP, cacheBannedValue, duration)
|
||||||
} else {
|
} else {
|
||||||
cache.Set(clientIP, cacheNoBannedValue, duration)
|
cache.Set(clientIP, cacheNoBannedValue, duration)
|
||||||
@@ -245,7 +252,7 @@ func handleNoStreamCache(a *Bouncer, rw http.ResponseWriter, req *http.Request,
|
|||||||
var decisions []Decision
|
var decisions []Decision
|
||||||
err := json.Unmarshal(body, &decisions)
|
err := json.Unmarshal(body, &decisions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to parse body: %s", err)
|
logger(fmt.Sprintf("failed to parse body: %s", err))
|
||||||
rw.WriteHeader(http.StatusForbidden)
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -259,24 +266,21 @@ func handleNoStreamCache(a *Bouncer, rw http.ResponseWriter, req *http.Request,
|
|||||||
rw.WriteHeader(http.StatusForbidden)
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
duration, err := time.ParseDuration(decisions[0].Duration)
|
duration, err := time.ParseDuration(decisions[0].Duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to parse duration: %s", err)
|
logger(fmt.Sprintf("failed to parse duration: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setDecision(a.cache, remoteHost, true, int64(duration.Seconds()))
|
setDecision(a.cache, remoteHost, true, int64(duration.Seconds()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleStreamCache(a *Bouncer, initialized bool) {
|
func handleStreamCache(a *Bouncer) {
|
||||||
// TODO clean properly on exit.
|
// TODO clean properly on exit.
|
||||||
time.AfterFunc(time.Duration(a.updateInterval)*time.Second, func() {
|
|
||||||
handleStreamCache(a, false)
|
|
||||||
})
|
|
||||||
var rawQuery string
|
var rawQuery string
|
||||||
var path string
|
var path string
|
||||||
if a.crowdsecMode == aloneMode {
|
if a.crowdsecMode == aloneMode {
|
||||||
rawQuery = ""
|
rawQuery = ""
|
||||||
path = crowdsecCapiDecisions
|
path = crowdsecCapiDecisions
|
||||||
} else {
|
} else {
|
||||||
rawQuery = fmt.Sprintf("startup=%t", initialized)
|
rawQuery = fmt.Sprintf("startup=%t", !a.crowdsecStreamHealthy)
|
||||||
path = crowdsecLapiStreamRoute
|
path = crowdsecLapiStreamRoute
|
||||||
}
|
}
|
||||||
streamRouteURL := url.URL{
|
streamRouteURL := url.URL{
|
||||||
@@ -289,7 +293,7 @@ func handleStreamCache(a *Bouncer, initialized bool) {
|
|||||||
var stream Stream
|
var stream Stream
|
||||||
err := json.Unmarshal(body, &stream)
|
err := json.Unmarshal(body, &stream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error while parsing body: %s", err)
|
logger(fmt.Sprintf("error while parsing body: %s", err))
|
||||||
a.crowdsecStreamHealthy = false
|
a.crowdsecStreamHealthy = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -315,7 +319,7 @@ func getToken(a *Bouncer) {
|
|||||||
var login Login
|
var login Login
|
||||||
err := json.Unmarshal(body, &login)
|
err := json.Unmarshal(body, &login)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error while parsing body: %s", err)
|
logger(fmt.Sprintf("error while parsing body: %s", err))
|
||||||
a.crowdsecStreamHealthy = false
|
a.crowdsecStreamHealthy = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -344,7 +348,7 @@ func crowdsecQuery(a *Bouncer, stringURL string, isPost bool) []byte {
|
|||||||
}
|
}
|
||||||
res, err := a.client.Do(req)
|
res, err := a.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error while fetching %v: %s", stringURL, err)
|
logger(fmt.Sprintf("error while fetching %v: %s", stringURL, err))
|
||||||
a.crowdsecStreamHealthy = false
|
a.crowdsecStreamHealthy = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -358,19 +362,19 @@ func crowdsecQuery(a *Bouncer, stringURL string, isPost bool) []byte {
|
|||||||
return crowdsecQuery(a, stringURL, false)
|
return crowdsecQuery(a, stringURL, false)
|
||||||
}
|
}
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
log.Printf("error while fetching %v, status code: %d", stringURL, res.StatusCode)
|
logger(fmt.Sprintf("error while fetching %v, status code: %d", stringURL, res.StatusCode))
|
||||||
a.crowdsecStreamHealthy = false
|
a.crowdsecStreamHealthy = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer func(body io.ReadCloser) {
|
defer func(body io.ReadCloser) {
|
||||||
err = body.Close()
|
err = body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to close body reader: %s", err)
|
logger(fmt.Sprintf("failed to close body reader: %s", err))
|
||||||
}
|
}
|
||||||
}(res.Body)
|
}(res.Body)
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error while reading body: %s", err)
|
logger(fmt.Sprintf("error while reading body: %s", err))
|
||||||
a.crowdsecStreamHealthy = false
|
a.crowdsecStreamHealthy = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-17
@@ -7,7 +7,7 @@ services:
|
|||||||
command:
|
command:
|
||||||
# - "--log.level=DEBUG"
|
# - "--log.level=DEBUG"
|
||||||
- "--accesslog"
|
- "--accesslog"
|
||||||
- "--accesslog.filepath=/var/log/traefik/traefik.log"
|
- "--accesslog.filepath=/var/log/traefik/access.log"
|
||||||
- "--api.insecure=true"
|
- "--api.insecure=true"
|
||||||
- "--providers.docker=true"
|
- "--providers.docker=true"
|
||||||
- "--providers.docker.exposedbydefault=false"
|
- "--providers.docker.exposedbydefault=false"
|
||||||
@@ -15,28 +15,40 @@ services:
|
|||||||
|
|
||||||
- "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
- "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||||
volumes:
|
volumes:
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- "logs:/var/log/traefik"
|
- logs:/var/log/traefik
|
||||||
- ./:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
- ./:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||||
ports:
|
|
||||||
- 8000:80
|
|
||||||
- 8080:8080
|
|
||||||
depends_on:
|
|
||||||
- 'crowdsec'
|
|
||||||
|
|
||||||
whoami:
|
|
||||||
image: traefik/whoami
|
|
||||||
container_name: "simple-service"
|
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.whoami.rule=Host(`localhost`)"
|
|
||||||
- "traefik.http.routers.whoami.entrypoints=web"
|
|
||||||
- "traefik.http.routers.whoami.middlewares=crowdsec@docker"
|
|
||||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
||||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseccapilogin=3829a6c9870e4726a377d8951ebb64a1m8psGvMaq1ykJ3zX"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseccapilogin=3829a6c9870e4726a377d8951ebb64a1m8psGvMaq1ykJ3zX"
|
||||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseccapipwd=Q5pgN8bRNInHGdx6QCksdPOJVLeLQ7ipJntSeuP3r8088zXzRVs4G8liXAKfI1k6"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseccapipwd=Q5pgN8bRNInHGdx6QCksdPOJVLeLQ7ipJntSeuP3r8088zXzRVs4G8liXAKfI1k6"
|
||||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseccapiscenarios=crowdsecurity/http-backdoors-attempts,baudneo/zoneminder-bf"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseccapiscenarios=crowdsecurity/http-backdoors-attempts,baudneo/zoneminder-bf"
|
||||||
|
ports:
|
||||||
|
- 8000:80
|
||||||
|
- 8080:8080
|
||||||
|
depends_on:
|
||||||
|
- crowdsec
|
||||||
|
|
||||||
|
whoami1:
|
||||||
|
image: traefik/whoami
|
||||||
|
container_name: "simple-service1"
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.whoami.rule=Host(`localhost`)"
|
||||||
|
- "traefik.http.routers.whoami.entrypoints=web"
|
||||||
|
- "traefik.http.routers.whoami.middlewares=crowdsec@docker"
|
||||||
|
- "traefik.http.services.whoami.loadbalancer.server.port=80"
|
||||||
|
whoami2:
|
||||||
|
image: traefik/whoami
|
||||||
|
container_name: "simple-service2"
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.whoami.rule=Host(`localhost`)"
|
||||||
|
- "traefik.http.routers.whoami.entrypoints=web"
|
||||||
|
- "traefik.http.routers.whoami.middlewares=crowdsec@docker"
|
||||||
|
- "traefik.http.services.whoami.loadbalancer.server.port=80"
|
||||||
|
|
||||||
crowdsec:
|
crowdsec:
|
||||||
image: crowdsecurity/crowdsec:v1.4.1
|
image: crowdsecurity/crowdsec:v1.4.1
|
||||||
@@ -49,8 +61,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||||
- logs:/var/log/traefik:ro
|
- logs:/var/log/traefik:ro
|
||||||
- crowdsec-db:/var/lib/crowdsec/data/
|
- db:/var/lib/crowdsec/data/
|
||||||
- crowdsec-config:/etc/crowdsec/
|
- config:/etc/crowdsec/
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
logs:
|
logs:
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ services:
|
|||||||
command:
|
command:
|
||||||
# - "--log.level=DEBUG"
|
# - "--log.level=DEBUG"
|
||||||
- "--accesslog"
|
- "--accesslog"
|
||||||
- "--accesslog.filepath=/var/log/traefik/traefik.log"
|
- "--accesslog.filepath=/var/log/traefik/access.log"
|
||||||
- "--api.insecure=true"
|
- "--api.insecure=true"
|
||||||
- "--providers.docker=true"
|
- "--providers.docker=true"
|
||||||
- "--providers.docker.exposedbydefault=false"
|
- "--providers.docker.exposedbydefault=false"
|
||||||
|
|||||||
Reference in New Issue
Block a user