mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-09-02 20:28:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1de1c924e | ||
|
|
36e6043c32 | ||
|
|
123cf15434 | ||
|
|
f89c5e25a9 | ||
|
|
58946d9fa2 | ||
|
|
6187a722ca |
@@ -1,47 +0,0 @@
|
||||
name: Go Matrix
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
cross:
|
||||
name: Go
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.19, 1.x ]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
# https://github.com/marketplace/actions/setup-go-environment
|
||||
- name: Set up Go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
# https://github.com/marketplace/actions/checkout
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# https://github.com/marketplace/actions/cache
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# In order:
|
||||
# * Module download cache
|
||||
# * Build cache (Linux)
|
||||
# * Build cache (Mac)
|
||||
# * Build cache (Windows)
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
~/Library/Caches/go-build
|
||||
%LocalAppData%\go-build
|
||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
||||
|
||||
- name: Test
|
||||
run: go test -v -cover ./...
|
||||
@@ -12,9 +12,9 @@ jobs:
|
||||
name: Main Process
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO_VERSION: 1.19
|
||||
GOLANGCI_LINT_VERSION: v1.50.0
|
||||
YAEGI_VERSION: v0.14.2
|
||||
GO_VERSION: 1.22
|
||||
GOLANGCI_LINT_VERSION: v1.57.2
|
||||
YAEGI_VERSION: v0.16.1
|
||||
CGO_ENABLED: 0
|
||||
defaults:
|
||||
run:
|
||||
|
||||
+25
-5
@@ -1,15 +1,11 @@
|
||||
run:
|
||||
timeout: 3m
|
||||
skip-files: []
|
||||
skip-dirs: []
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
- fieldalignment
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
goconst:
|
||||
@@ -25,6 +21,31 @@ linters-settings:
|
||||
- FIXME
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
depguard:
|
||||
rules:
|
||||
Main:
|
||||
files:
|
||||
- $all
|
||||
- "!$test"
|
||||
allow:
|
||||
- $gostd
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/configuration
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/captcha
|
||||
- github.com/leprosus/golang-ttl-map
|
||||
- github.com/maxlerebourg/simpleredis
|
||||
Test:
|
||||
files:
|
||||
- $test
|
||||
allow:
|
||||
- $gostd
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/configuration
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache
|
||||
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/captcha
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
@@ -67,7 +88,6 @@ linters:
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-per-linter: 0
|
||||
max-same-issues: 0
|
||||
exclude:
|
||||
- "G402: TLS InsecureSkipVerify may be true."
|
||||
|
||||
@@ -36,7 +36,7 @@ On successfull completion, he will be cleaned for a specified period of time bef
|
||||
The following captcha providers are supported now:
|
||||
- [hcaptcha](https://www.hcaptcha.com/)
|
||||
- [recaptcha](https://www.google.com/recaptcha/about/)
|
||||
- [turnstile](https://www.cloudflare.com/fr-fr/products/turnstile/)
|
||||
- [turnstile](https://www.cloudflare.com/products/turnstile/)
|
||||
|
||||
|
||||
There are 5 operating modes (CrowdsecMode) for this plugin:
|
||||
@@ -51,7 +51,7 @@ There are 5 operating modes (CrowdsecMode) for this plugin:
|
||||
|
||||
The `streaming mode` is recommended for performance, decisions are updated every 60 sec by default and that's the only communication between Traefik and Crowdsec. Every request that happens hits the cache for quick decisions.
|
||||
|
||||
The cache can be local to Traefik using the filesystem, or a separate Redis instance.
|
||||
The cache can be local to Traefik in memory or using a separate Redis instance.
|
||||
|
||||
Below are Mermaid diagrams detailling how each mode work:
|
||||
|
||||
@@ -333,6 +333,10 @@ Only one instance of the plugin is *possible*.
|
||||
- bool
|
||||
- default: true
|
||||
- Block request when Crowdsec Appsec Server have a [status 500](https://docs.crowdsec.net/docs/next/appsec/protocol#response-code).
|
||||
- CrowdsecAppsecUnreachableBlock
|
||||
- bool
|
||||
- default: true
|
||||
- Block request when Crowdsec Appsec Server is unreachable.
|
||||
- CrowdsecLapiScheme
|
||||
- string
|
||||
- default: `http`, expected values are: `http`, `https`
|
||||
@@ -375,7 +379,7 @@ Only one instance of the plugin is *possible*.
|
||||
- RedisCacheEnabled
|
||||
- bool
|
||||
- default: false
|
||||
- enable Redis cache instead of filesystem cache
|
||||
- enable Redis cache instead of in-memory cache
|
||||
- RedisCacheHost
|
||||
- string
|
||||
- default: "redis:6379"
|
||||
@@ -486,6 +490,7 @@ http:
|
||||
crowdsecAppsecEnabled: false
|
||||
crowdsecAppsecHost: crowdsec:7422
|
||||
crowdsecAppsecFailureBlock: true
|
||||
crowdsecAppsecUnreachableBlock: true
|
||||
crowdsecLapiKey: privateKey-foo
|
||||
crowdsecLapiKeyFile: /etc/traefik/cs-privateKey-foo
|
||||
crowdsecLapiHost: crowdsec:8080
|
||||
|
||||
+24
-17
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
// It can cache results in memory or using redis, or even ask crowdsec for every requests.
|
||||
package crowdsec_bouncer_traefik_plugin //nolint:revive,stylecheck
|
||||
|
||||
import (
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -62,6 +63,7 @@ type Bouncer struct {
|
||||
appsecEnabled bool
|
||||
appsecHost string
|
||||
appsecFailureBlock bool
|
||||
appsecUnreachableBlock bool
|
||||
crowdsecScheme string
|
||||
crowdsecHost string
|
||||
crowdsecKey string
|
||||
@@ -85,11 +87,11 @@ type Bouncer struct {
|
||||
}
|
||||
|
||||
// New creates the crowdsec bouncer plugin.
|
||||
func New(ctx context.Context, next http.Handler, config *configuration.Config, name string) (http.Handler, error) {
|
||||
func New(_ context.Context, next http.Handler, config *configuration.Config, name string) (http.Handler, error) {
|
||||
log := logger.New(config.LogLevel)
|
||||
err := configuration.ValidateParams(config)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("New:validateParams %s", err.Error()))
|
||||
log.Error("New:validateParams " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -112,12 +114,12 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
|
||||
crowdsecHeader = crowdsecLapiHeader
|
||||
tlsConfig, err = configuration.GetTLSConfigCrowdsec(config, log)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("New:getTLSConfigCrowdsec fail to get tlsConfig %s", err.Error()))
|
||||
log.Error("New:getTLSConfigCrowdsec fail to get tlsConfig " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
apiKey, errAPIKey := configuration.GetVariable(config, "CrowdsecLapiKey")
|
||||
if errAPIKey != nil && len(tlsConfig.Certificates) == 0 {
|
||||
log.Error(fmt.Sprintf("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup %s", errAPIKey.Error()))
|
||||
log.Error("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup " + errAPIKey.Error())
|
||||
return nil, err
|
||||
}
|
||||
config.CrowdsecLapiKey = apiKey
|
||||
@@ -129,7 +131,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
|
||||
banTemplate, _ := configuration.GetHTMLTemplate(config.BanHTMLFilePath)
|
||||
err = banTemplate.Execute(&buf, nil)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("New:banTemplate is bad formatted %s", err.Error()))
|
||||
log.Error("New:banTemplate is bad formatted " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
banTemplateString = buf.String()
|
||||
@@ -145,6 +147,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
|
||||
appsecEnabled: config.CrowdsecAppsecEnabled,
|
||||
appsecHost: config.CrowdsecAppsecHost,
|
||||
appsecFailureBlock: config.CrowdsecAppsecFailureBlock,
|
||||
appsecUnreachableBlock: config.CrowdsecAppsecUnreachableBlock,
|
||||
crowdsecScheme: config.CrowdsecLapiScheme,
|
||||
crowdsecHost: config.CrowdsecLapiHost,
|
||||
crowdsecKey: config.CrowdsecLapiKey,
|
||||
@@ -209,7 +212,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
|
||||
if (config.CrowdsecMode == configuration.StreamMode || config.CrowdsecMode == configuration.AloneMode) && ticker == nil {
|
||||
if config.CrowdsecMode == configuration.AloneMode {
|
||||
if err := getToken(bouncer); err != nil {
|
||||
bouncer.log.Error(fmt.Sprintf("New:getToken %s", err.Error()))
|
||||
bouncer.log.Error("New:getToken " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -219,7 +222,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
|
||||
handleStreamTicker(bouncer)
|
||||
})
|
||||
}
|
||||
bouncer.log.Debug(fmt.Sprintf("New initialized mode:%s", config.CrowdsecMode))
|
||||
bouncer.log.Debug("New initialized mode:" + config.CrowdsecMode)
|
||||
|
||||
return bouncer, nil
|
||||
}
|
||||
@@ -443,7 +446,7 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) (string, error) {
|
||||
case "captcha":
|
||||
value = cache.CaptchaValue
|
||||
default:
|
||||
bouncer.log.Debug(fmt.Sprintf("handleStreamCache:unknownType %s", decision.Type))
|
||||
bouncer.log.Debug("handleStreamCache:unknownType " + decision.Type)
|
||||
}
|
||||
if isLiveMode {
|
||||
durationSecond := int64(duration.Seconds())
|
||||
@@ -452,7 +455,7 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) (string, error) {
|
||||
}
|
||||
bouncer.cacheClient.Set(remoteIP, value, durationSecond)
|
||||
}
|
||||
return value, fmt.Errorf("handleNoStreamCache:banned")
|
||||
return value, errors.New("handleNoStreamCache:banned")
|
||||
}
|
||||
|
||||
func getToken(bouncer *Bouncer) error {
|
||||
@@ -517,7 +520,7 @@ func handleStreamCache(bouncer *Bouncer) error {
|
||||
case "captcha":
|
||||
value = cache.CaptchaValue
|
||||
default:
|
||||
bouncer.log.Debug(fmt.Sprintf("handleStreamCache:unknownType %s", decision.Type))
|
||||
bouncer.log.Debug("handleStreamCache:unknownType " + decision.Type)
|
||||
}
|
||||
bouncer.cacheClient.Set(decision.Value, value, int64(duration.Seconds()))
|
||||
}
|
||||
@@ -545,11 +548,11 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string, isPost bool) ([]byte, err
|
||||
req.Header.Add(bouncer.crowdsecHeader, bouncer.crowdsecKey)
|
||||
res, err := bouncer.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("crowdsecQuery url:%s %w", stringURL, err)
|
||||
return nil, fmt.Errorf("crowdsecQuery:unreachable url:%s %w", stringURL, err)
|
||||
}
|
||||
defer func() {
|
||||
if err = res.Body.Close(); err != nil {
|
||||
bouncer.log.Error(fmt.Sprintf("crowdsecQuery:closeBody %s", err.Error()))
|
||||
bouncer.log.Error("crowdsecQuery:closeBody " + err.Error())
|
||||
}
|
||||
}()
|
||||
if res.StatusCode == http.StatusUnauthorized && bouncer.crowdsecMode == configuration.AloneMode {
|
||||
@@ -601,17 +604,21 @@ func appsecQuery(bouncer *Bouncer, ip string, httpReq *http.Request) error {
|
||||
|
||||
res, err := bouncer.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("appsecQuery %w", err)
|
||||
bouncer.log.Error("appsecQuery:unreachable")
|
||||
if bouncer.appsecUnreachableBlock {
|
||||
return fmt.Errorf("appsecQuery:unreachable %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
if err = res.Body.Close(); err != nil {
|
||||
bouncer.log.Error(fmt.Sprintf("appsecQuery:closeBody %s", err.Error()))
|
||||
bouncer.log.Error("appsecQuery:closeBody " + err.Error())
|
||||
}
|
||||
}()
|
||||
if res.StatusCode == http.StatusInternalServerError {
|
||||
bouncer.log.Debug("crowdsecQuery statusCode:500")
|
||||
bouncer.log.Info("appsecQuery:failure")
|
||||
if bouncer.appsecFailureBlock {
|
||||
return fmt.Errorf("appsecQuery statusCode:%d", res.StatusCode)
|
||||
return errors.New("appsecQuery statusCode:500")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ func TestServeHTTP(t *testing.T) {
|
||||
cfg.CrowdsecLapiKey = "test"
|
||||
|
||||
ctx := context.Background()
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})
|
||||
next := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})
|
||||
|
||||
handler, err := New(ctx, next, cfg, "demo-plugin")
|
||||
if err != nil {
|
||||
@@ -93,7 +93,7 @@ func TestBouncer_ServeHTTP(t *testing.T) {
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Run(tt.name, func(_ *testing.T) {
|
||||
bouncer := &Bouncer{
|
||||
next: tt.fields.next,
|
||||
name: tt.fields.name,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||
|
||||
go 1.19
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/leprosus/golang-ttl-map v1.1.7
|
||||
github.com/maxlerebourg/simpleredis v1.0.9
|
||||
github.com/maxlerebourg/simpleredis v1.0.11
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
github.com/leprosus/golang-ttl-map v1.1.7 h1:cF4AAFDDnJTFSV+/42sKLhmMluvLdRlCGS2UaifH6UM=
|
||||
github.com/leprosus/golang-ttl-map v1.1.7/go.mod h1:4QWHJPeVBbrkhOhXdhCv9IEiyj/YzkO04/iexy4vSe0=
|
||||
github.com/maxlerebourg/simpleredis v1.0.9 h1:aj1hKaYPeOVE4Ksu3TV/zsreUDDWOpKXBAvoFysiqII=
|
||||
github.com/maxlerebourg/simpleredis v1.0.9/go.mod h1:/DH8zOK6kDskSqoX/m5CJJdNGfkIQZd/ERBJgytDDSk=
|
||||
github.com/maxlerebourg/simpleredis v1.0.11 h1:B33TUeIrHtJH2/Qj2bRdU+UZ1BvZwFyP55JWMxHirWg=
|
||||
github.com/maxlerebourg/simpleredis v1.0.11/go.mod h1:lT4LX02SOsE9PxUcSrz1QW5ZnO86gPbaiYBxmtcXEls=
|
||||
|
||||
Vendored
+5
-4
@@ -3,6 +3,7 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
ttl_map "github.com/leprosus/golang-ttl-map"
|
||||
@@ -38,7 +39,7 @@ func (localCache) get(key string) (string, error) {
|
||||
if isCached && isValid && len(valueString) > 0 {
|
||||
return valueString, nil
|
||||
}
|
||||
return "", fmt.Errorf(CacheMiss)
|
||||
return "", errors.New(CacheMiss)
|
||||
}
|
||||
|
||||
func (localCache) set(key, value string, duration int64) {
|
||||
@@ -60,20 +61,20 @@ func (redisCache) get(key string) (string, error) {
|
||||
return valueString, nil
|
||||
}
|
||||
if err.Error() == simpleredis.RedisMiss {
|
||||
return "", fmt.Errorf(CacheMiss)
|
||||
return "", errors.New(CacheMiss)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (rc redisCache) set(key, value string, duration int64) {
|
||||
if err := redis.Set(key, []byte(value), duration); err != nil {
|
||||
rc.log.Error(fmt.Sprintf("cache:setDecisionRedisCache %s", err.Error()))
|
||||
rc.log.Error("cache:setDecisionRedisCache" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (rc redisCache) delete(key string) {
|
||||
if err := redis.Del(key); err != nil {
|
||||
rc.log.Error(fmt.Sprintf("cache:deleteDecisionRedisCache %s", err.Error()))
|
||||
rc.log.Error("cache:deleteDecisionRedisCache " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ var (
|
||||
},
|
||||
configuration.TurnstileProvider: {
|
||||
js: "https://challenges.cloudflare.com/turnstile/v0/api.js",
|
||||
key: "cf-captcha",
|
||||
key: "cf-turnstile",
|
||||
validate: "https://challenges.cloudflare.com/turnstile/v0/siteverify",
|
||||
},
|
||||
}
|
||||
@@ -76,13 +76,13 @@ func (c *Client) New(log *logger.Log, cacheClient *cache.Client, httpClient *htt
|
||||
func (c *Client) ServeHTTP(rw http.ResponseWriter, r *http.Request, remoteIP string) {
|
||||
valid, err := c.Validate(r)
|
||||
if err != nil {
|
||||
c.log.Debug(fmt.Sprintf("captcha:ServeHTTP:validate %s", err.Error()))
|
||||
c.log.Info("captcha:ServeHTTP:validate " + err.Error())
|
||||
rw.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if valid {
|
||||
c.log.Debug("captcha:ServeHTTP captcha:valid")
|
||||
c.cacheClient.Set(fmt.Sprintf("%s_captcha", remoteIP), cache.CaptchaDoneValue, c.gracePeriodSeconds)
|
||||
c.cacheClient.Set(remoteIP+"_captcha", cache.CaptchaDoneValue, c.gracePeriodSeconds)
|
||||
http.Redirect(rw, r, r.URL.String(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
@@ -94,13 +94,13 @@ func (c *Client) ServeHTTP(rw http.ResponseWriter, r *http.Request, remoteIP str
|
||||
"FrontendKey": captcha[c.provider].key,
|
||||
})
|
||||
if err != nil {
|
||||
c.log.Info(fmt.Sprintf("captcha:ServeHTTP captchaTemplateServe %s", err.Error()))
|
||||
c.log.Info("captcha:ServeHTTP captchaTemplateServe " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Check Verify if the captcha is already done.
|
||||
func (c *Client) Check(remoteIP string) bool {
|
||||
value, _ := c.cacheClient.Get(fmt.Sprintf("%s_captcha", remoteIP))
|
||||
value, _ := c.cacheClient.Get(remoteIP + "_captcha")
|
||||
passed := value == cache.CaptchaDoneValue
|
||||
c.log.Debug(fmt.Sprintf("captcha:Check ip:%s pass:%v", remoteIP, passed))
|
||||
return passed
|
||||
@@ -113,10 +113,10 @@ type responseProvider struct {
|
||||
// Validate Verify the captcha from provider API.
|
||||
func (c *Client) Validate(r *http.Request) (bool, error) {
|
||||
if r.Method != http.MethodPost {
|
||||
c.log.Debug(fmt.Sprintf("captcha:Validate invalid method: %s", r.Method))
|
||||
c.log.Debug("captcha:Validate invalid method: " + r.Method)
|
||||
return false, nil
|
||||
}
|
||||
var response = r.FormValue(fmt.Sprintf("%s-response", captcha[c.provider].key))
|
||||
var response = r.FormValue(captcha[c.provider].key + "-response")
|
||||
if response == "" {
|
||||
c.log.Debug("captcha:Validate no captcha response found in request")
|
||||
return false, nil
|
||||
@@ -130,7 +130,7 @@ func (c *Client) Validate(r *http.Request) (bool, error) {
|
||||
}
|
||||
defer func() {
|
||||
if err = res.Body.Close(); err != nil {
|
||||
c.log.Error(fmt.Sprintf("captcha:Validate %s", err.Error()))
|
||||
c.log.Error("captcha:Validate " + err.Error())
|
||||
}
|
||||
}()
|
||||
if !strings.Contains(res.Header.Get("content-type"), "application/json") {
|
||||
|
||||
@@ -4,6 +4,7 @@ package configuration
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
@@ -40,6 +41,7 @@ type Config struct {
|
||||
CrowdsecAppsecEnabled bool `json:"crowdsecAppsecEnabled,omitempty"`
|
||||
CrowdsecAppsecHost string `json:"crowdsecAppsecHost,omitempty"`
|
||||
CrowdsecAppsecFailureBlock bool `json:"crowdsecAppsecFailureBlock,omitempty"`
|
||||
CrowdsecAppsecUnreachableBlock bool `json:"crowdsecAppsecUnreachableBlock,omitempty"`
|
||||
CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"`
|
||||
CrowdsecLapiHost string `json:"crowdsecLapiHost,omitempty"`
|
||||
CrowdsecLapiKey string `json:"crowdsecLapiKey,omitempty"`
|
||||
@@ -96,6 +98,7 @@ func New() *Config {
|
||||
CrowdsecAppsecEnabled: false,
|
||||
CrowdsecAppsecHost: "crowdsec:7422",
|
||||
CrowdsecAppsecFailureBlock: true,
|
||||
CrowdsecAppsecUnreachableBlock: true,
|
||||
CrowdsecLapiScheme: HTTP,
|
||||
CrowdsecLapiHost: "crowdsec:8080",
|
||||
CrowdsecLapiKey: "",
|
||||
@@ -124,7 +127,7 @@ func New() *Config {
|
||||
func GetVariable(config *Config, key string) (string, error) {
|
||||
value := ""
|
||||
object := reflect.Indirect(reflect.ValueOf(config))
|
||||
field := object.FieldByName(fmt.Sprintf("%sFile", key))
|
||||
field := object.FieldByName(key + "File")
|
||||
// Here linter say you should simplify this code, but lets not, performance is important not clarity and complexity
|
||||
fp := field.String()
|
||||
if fp != "" {
|
||||
@@ -151,7 +154,7 @@ func GetVariable(config *Config, key string) (string, error) {
|
||||
func GetHTMLTemplate(path string) (*template.Template, error) {
|
||||
var err error
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("no html template provided")
|
||||
return nil, errors.New("no html template provided")
|
||||
}
|
||||
//nolint:gosec
|
||||
b, err := os.ReadFile(path)
|
||||
@@ -234,7 +237,7 @@ func ValidateParams(config *Config) error {
|
||||
}
|
||||
// We need to either have crowdsecLapiKey defined or the BouncerCert and Bouncerkey
|
||||
if lapiKey == "" && (certBouncer == "" || certBouncerKey == "") {
|
||||
return fmt.Errorf("CrowdsecLapiKey || (CrowdsecLapiTLSCertificateBouncer && CrowdsecLapiTLSCertificateBouncerKey): cannot be all empty")
|
||||
return errors.New("CrowdsecLapiKey || (CrowdsecLapiTLSCertificateBouncer && CrowdsecLapiTLSCertificateBouncerKey): cannot be all empty")
|
||||
} else if lapiKey != "" && (certBouncer == "" || certBouncerKey == "") {
|
||||
lapiKey = strings.TrimSpace(lapiKey)
|
||||
if err = validateParamsAPIKey(lapiKey); err != nil {
|
||||
@@ -267,7 +270,7 @@ func validateURL(variable, scheme, host string) error {
|
||||
// See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
|
||||
func validateParamsAPIKey(lapiKey string) error {
|
||||
reg := regexp.MustCompile("^[a-zA-Z0-9 !#$%&'*+-.^_`|~=/]*$")
|
||||
if !reg.Match([]byte(lapiKey)) {
|
||||
if !reg.MatchString(lapiKey) {
|
||||
return fmt.Errorf("CrowdsecLapiKey doesn't valid this regexp: '/%s/'", reg.String())
|
||||
}
|
||||
return nil
|
||||
@@ -279,12 +282,12 @@ func validateParamsTLS(config *Config) error {
|
||||
return err
|
||||
}
|
||||
if certAuth == "" {
|
||||
return fmt.Errorf("CrowdsecLapiTLSCertificateAuthority must be specified when CrowdsecLapiScheme='https' and CrowdsecLapiTLSInsecureVerify=false")
|
||||
return errors.New("CrowdsecLapiTLSCertificateAuthority must be specified when CrowdsecLapiScheme='https' and CrowdsecLapiTLSInsecureVerify=false")
|
||||
}
|
||||
tlsConfig := new(tls.Config)
|
||||
tlsConfig.RootCAs = x509.NewCertPool()
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuth)) {
|
||||
return fmt.Errorf("failed parsing pem file")
|
||||
return errors.New("failed parsing pem file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -321,17 +324,17 @@ func validateParamsRequired(config *Config) error {
|
||||
}
|
||||
}
|
||||
if config.UpdateMaxFailure < -1 {
|
||||
return fmt.Errorf("UpdateMaxFailure: cannot be less than -1")
|
||||
return errors.New("UpdateMaxFailure: cannot be less than -1")
|
||||
}
|
||||
|
||||
if !contains([]string{NoneMode, LiveMode, StreamMode, AloneMode, AppsecMode}, config.CrowdsecMode) {
|
||||
return fmt.Errorf("CrowdsecMode: must be one of 'none', 'live', 'stream', 'alone' or 'appsec'")
|
||||
return errors.New("CrowdsecMode: must be one of 'none', 'live', 'stream', 'alone' or 'appsec'")
|
||||
}
|
||||
if !contains([]string{HTTP, HTTPS}, config.CrowdsecLapiScheme) {
|
||||
return fmt.Errorf("CrowdsecLapiScheme: must be one of 'http' or 'https'")
|
||||
return errors.New("CrowdsecLapiScheme: must be one of 'http' or 'https'")
|
||||
}
|
||||
if !contains([]string{"", HcaptchaProvider, RecaptchaProvider, TurnstileProvider}, config.CaptchaProvider) {
|
||||
return fmt.Errorf("CrowdsecLapiScheme: must be one of 'hcaptcha', 'recaptcha' or 'turnstile'")
|
||||
return errors.New("CaptchaProvider: must be one of 'hcaptcha', 'recaptcha' or 'turnstile'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -360,7 +363,7 @@ func GetTLSConfigCrowdsec(config *Config, log *logger.Log) (*tls.Config, error)
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuthority)) {
|
||||
// here we return because if CrowdsecLapiTLSInsecureVerify is false
|
||||
// and CA not load, we can't communicate with https
|
||||
return nil, fmt.Errorf("getTLSConfigCrowdsec:cannot load CA and verify cert is enabled")
|
||||
return nil, errors.New("getTLSConfigCrowdsec:cannot load CA and verify cert is enabled")
|
||||
}
|
||||
log.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSCertificateAuthority CA added successfully")
|
||||
}
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@
|
||||
package ip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -45,7 +46,7 @@ func NewChecker(log *logger.Log, trustedIPs []string) (*Checker, error) {
|
||||
// Contains checks if provided address is in the trusted IPs.
|
||||
func (ip *Checker) Contains(addr string) (bool, error) {
|
||||
if len(addr) == 0 {
|
||||
return false, fmt.Errorf("Contains:noAddress")
|
||||
return false, errors.New("Contains:noAddress")
|
||||
}
|
||||
|
||||
ipAddr, err := parseIP(addr)
|
||||
|
||||
-1
@@ -122,7 +122,6 @@ func (sr *SimpleRedis) askRedis(cmd redisCmd, channel chan redisCmd) redisCmd {
|
||||
}
|
||||
read, _ = reader.ReadLineBytes()
|
||||
return redisCmd{Data: read}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -1,6 +1,6 @@
|
||||
# github.com/leprosus/golang-ttl-map v1.1.7
|
||||
## explicit; go 1.15
|
||||
github.com/leprosus/golang-ttl-map
|
||||
# github.com/maxlerebourg/simpleredis v1.0.9
|
||||
## explicit; go 1.19
|
||||
# github.com/maxlerebourg/simpleredis v1.0.11
|
||||
## explicit; go 1.22
|
||||
github.com/maxlerebourg/simpleredis
|
||||
|
||||
Reference in New Issue
Block a user