Compare commits

..
6 Commits
Author SHA1 Message Date
maxlerebourgandmax.lerebourg f1de1c924e 🔨 174 fix classname for Turnstile (#178)
Co-authored-by: max.lerebourg <max.lerebourg@monisnap.com>
2024-06-19 20:13:49 +02:00
mathieuHa 36e6043c32 📝 doc(cache) Specify that local cache is in memory and not in the filesystem (#177) 2024-06-09 16:20:55 +02:00
maxlerebourg 123cf15434 Add CrowdsecAppsecUnreachableBlock (#175)
*  Add CrowdsecAppsecUnreachableBlock

* 🍱 update readme

* 🍱 fix lint

* 🍱 fix lint
2024-06-09 10:59:45 +02:00
mathieuHa f89c5e25a9 ✏️ fix(validation) Replace typo on CaptchaProvider config validation (#176) 2024-06-09 10:47:17 +02:00
maxlerebourg 58946d9fa2 📄 Update README.md for lang doc (#170) 2024-05-22 11:24:11 +02:00
mathieuHaandMax Lerebourg 6187a722ca 167 feature update to go 122 (#168)
* ⬆️ Upgrade golang version

* 🚨 Optimize Lint for strings

* 🔒️ Add allow list of packages

* 🚨 Fix final lint

* 👷 Update ci

* 🍱 upgrade dependencies

* 🍱 fix comment

---------

Co-authored-by: Max Lerebourg <maxlerebourg@gmail.com>
2024-05-18 13:20:14 +02:00
14 changed files with 125 additions and 136 deletions
-47
View File
@@ -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 ./...
+3 -3
View File
@@ -12,9 +12,9 @@ jobs:
name: Main Process name: Main Process
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
GO_VERSION: 1.19 GO_VERSION: 1.22
GOLANGCI_LINT_VERSION: v1.50.0 GOLANGCI_LINT_VERSION: v1.57.2
YAEGI_VERSION: v0.14.2 YAEGI_VERSION: v0.16.1
CGO_ENABLED: 0 CGO_ENABLED: 0
defaults: defaults:
run: run:
+25 -5
View File
@@ -1,15 +1,11 @@
run: run:
timeout: 3m timeout: 3m
skip-files: []
skip-dirs: []
linters-settings: linters-settings:
govet: govet:
enable-all: true enable-all: true
disable: disable:
- fieldalignment - fieldalignment
golint:
min-confidence: 0
gocyclo: gocyclo:
min-complexity: 15 min-complexity: 15
goconst: goconst:
@@ -25,6 +21,31 @@ linters-settings:
- FIXME - FIXME
gofumpt: gofumpt:
extra-rules: true 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: linters:
enable-all: true enable-all: true
@@ -67,7 +88,6 @@ linters:
issues: issues:
exclude-use-default: false exclude-use-default: false
max-per-linter: 0
max-same-issues: 0 max-same-issues: 0
exclude: exclude:
- "G402: TLS InsecureSkipVerify may be true." - "G402: TLS InsecureSkipVerify may be true."
+8 -3
View File
@@ -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: The following captcha providers are supported now:
- [hcaptcha](https://www.hcaptcha.com/) - [hcaptcha](https://www.hcaptcha.com/)
- [recaptcha](https://www.google.com/recaptcha/about/) - [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: 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 `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: Below are Mermaid diagrams detailling how each mode work:
@@ -333,6 +333,10 @@ Only one instance of the plugin is *possible*.
- bool - bool
- default: true - default: true
- Block request when Crowdsec Appsec Server have a [status 500](https://docs.crowdsec.net/docs/next/appsec/protocol#response-code). - 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 - CrowdsecLapiScheme
- string - string
- default: `http`, expected values are: `http`, `https` - default: `http`, expected values are: `http`, `https`
@@ -375,7 +379,7 @@ Only one instance of the plugin is *possible*.
- RedisCacheEnabled - RedisCacheEnabled
- bool - bool
- default: false - default: false
- enable Redis cache instead of filesystem cache - enable Redis cache instead of in-memory cache
- RedisCacheHost - RedisCacheHost
- string - string
- default: "redis:6379" - default: "redis:6379"
@@ -486,6 +490,7 @@ http:
crowdsecAppsecEnabled: false crowdsecAppsecEnabled: false
crowdsecAppsecHost: crowdsec:7422 crowdsecAppsecHost: crowdsec:7422
crowdsecAppsecFailureBlock: true crowdsecAppsecFailureBlock: true
crowdsecAppsecUnreachableBlock: true
crowdsecLapiKey: privateKey-foo crowdsecLapiKey: privateKey-foo
crowdsecLapiKeyFile: /etc/traefik/cs-privateKey-foo crowdsecLapiKeyFile: /etc/traefik/cs-privateKey-foo
crowdsecLapiHost: crowdsec:8080 crowdsecLapiHost: crowdsec:8080
+24 -17
View File
@@ -1,5 +1,5 @@
// Package crowdsec_bouncer_traefik_plugin implements a middleware that communicates with crowdsec. // 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 package crowdsec_bouncer_traefik_plugin //nolint:revive,stylecheck
import ( import (
@@ -7,6 +7,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -62,6 +63,7 @@ type Bouncer struct {
appsecEnabled bool appsecEnabled bool
appsecHost string appsecHost string
appsecFailureBlock bool appsecFailureBlock bool
appsecUnreachableBlock bool
crowdsecScheme string crowdsecScheme string
crowdsecHost string crowdsecHost string
crowdsecKey string crowdsecKey string
@@ -85,11 +87,11 @@ type Bouncer struct {
} }
// New creates the crowdsec bouncer plugin. // 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) log := logger.New(config.LogLevel)
err := configuration.ValidateParams(config) err := configuration.ValidateParams(config)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("New:validateParams %s", err.Error())) log.Error("New:validateParams " + err.Error())
return nil, err return nil, err
} }
@@ -112,12 +114,12 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
crowdsecHeader = crowdsecLapiHeader crowdsecHeader = crowdsecLapiHeader
tlsConfig, err = configuration.GetTLSConfigCrowdsec(config, log) tlsConfig, err = configuration.GetTLSConfigCrowdsec(config, log)
if err != nil { 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 return nil, err
} }
apiKey, errAPIKey := configuration.GetVariable(config, "CrowdsecLapiKey") apiKey, errAPIKey := configuration.GetVariable(config, "CrowdsecLapiKey")
if errAPIKey != nil && len(tlsConfig.Certificates) == 0 { 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 return nil, err
} }
config.CrowdsecLapiKey = apiKey config.CrowdsecLapiKey = apiKey
@@ -129,7 +131,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
banTemplate, _ := configuration.GetHTMLTemplate(config.BanHTMLFilePath) banTemplate, _ := configuration.GetHTMLTemplate(config.BanHTMLFilePath)
err = banTemplate.Execute(&buf, nil) err = banTemplate.Execute(&buf, nil)
if err != 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 return nil, err
} }
banTemplateString = buf.String() banTemplateString = buf.String()
@@ -145,6 +147,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
appsecEnabled: config.CrowdsecAppsecEnabled, appsecEnabled: config.CrowdsecAppsecEnabled,
appsecHost: config.CrowdsecAppsecHost, appsecHost: config.CrowdsecAppsecHost,
appsecFailureBlock: config.CrowdsecAppsecFailureBlock, appsecFailureBlock: config.CrowdsecAppsecFailureBlock,
appsecUnreachableBlock: config.CrowdsecAppsecUnreachableBlock,
crowdsecScheme: config.CrowdsecLapiScheme, crowdsecScheme: config.CrowdsecLapiScheme,
crowdsecHost: config.CrowdsecLapiHost, crowdsecHost: config.CrowdsecLapiHost,
crowdsecKey: config.CrowdsecLapiKey, 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.StreamMode || config.CrowdsecMode == configuration.AloneMode) && ticker == nil {
if config.CrowdsecMode == configuration.AloneMode { if config.CrowdsecMode == configuration.AloneMode {
if err := getToken(bouncer); err != nil { 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 return nil, err
} }
} }
@@ -219,7 +222,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
handleStreamTicker(bouncer) handleStreamTicker(bouncer)
}) })
} }
bouncer.log.Debug(fmt.Sprintf("New initialized mode:%s", config.CrowdsecMode)) bouncer.log.Debug("New initialized mode:" + config.CrowdsecMode)
return bouncer, nil return bouncer, nil
} }
@@ -443,7 +446,7 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) (string, error) {
case "captcha": case "captcha":
value = cache.CaptchaValue value = cache.CaptchaValue
default: default:
bouncer.log.Debug(fmt.Sprintf("handleStreamCache:unknownType %s", decision.Type)) bouncer.log.Debug("handleStreamCache:unknownType " + decision.Type)
} }
if isLiveMode { if isLiveMode {
durationSecond := int64(duration.Seconds()) durationSecond := int64(duration.Seconds())
@@ -452,7 +455,7 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) (string, error) {
} }
bouncer.cacheClient.Set(remoteIP, value, durationSecond) bouncer.cacheClient.Set(remoteIP, value, durationSecond)
} }
return value, fmt.Errorf("handleNoStreamCache:banned") return value, errors.New("handleNoStreamCache:banned")
} }
func getToken(bouncer *Bouncer) error { func getToken(bouncer *Bouncer) error {
@@ -517,7 +520,7 @@ func handleStreamCache(bouncer *Bouncer) error {
case "captcha": case "captcha":
value = cache.CaptchaValue value = cache.CaptchaValue
default: 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())) 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) req.Header.Add(bouncer.crowdsecHeader, bouncer.crowdsecKey)
res, err := bouncer.httpClient.Do(req) res, err := bouncer.httpClient.Do(req)
if err != nil { 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() { defer func() {
if err = res.Body.Close(); err != nil { 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 { 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) res, err := bouncer.httpClient.Do(req)
if err != nil { 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() { defer func() {
if err = res.Body.Close(); err != nil { 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 { if res.StatusCode == http.StatusInternalServerError {
bouncer.log.Debug("crowdsecQuery statusCode:500") bouncer.log.Info("appsecQuery:failure")
if bouncer.appsecFailureBlock { if bouncer.appsecFailureBlock {
return fmt.Errorf("appsecQuery statusCode:%d", res.StatusCode) return errors.New("appsecQuery statusCode:500")
} }
return nil return nil
} }
+2 -2
View File
@@ -18,7 +18,7 @@ func TestServeHTTP(t *testing.T) {
cfg.CrowdsecLapiKey = "test" cfg.CrowdsecLapiKey = "test"
ctx := context.Background() 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") handler, err := New(ctx, next, cfg, "demo-plugin")
if err != nil { if err != nil {
@@ -93,7 +93,7 @@ func TestBouncer_ServeHTTP(t *testing.T) {
// TODO: Add test cases. // TODO: Add test cases.
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(_ *testing.T) {
bouncer := &Bouncer{ bouncer := &Bouncer{
next: tt.fields.next, next: tt.fields.next,
name: tt.fields.name, name: tt.fields.name,
+2 -2
View File
@@ -1,8 +1,8 @@
module github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin module github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
go 1.19 go 1.22
require ( require (
github.com/leprosus/golang-ttl-map v1.1.7 github.com/leprosus/golang-ttl-map v1.1.7
github.com/maxlerebourg/simpleredis v1.0.9 github.com/maxlerebourg/simpleredis v1.0.11
) )
+2 -2
View File
@@ -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 h1:cF4AAFDDnJTFSV+/42sKLhmMluvLdRlCGS2UaifH6UM=
github.com/leprosus/golang-ttl-map v1.1.7/go.mod h1:4QWHJPeVBbrkhOhXdhCv9IEiyj/YzkO04/iexy4vSe0= 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.11 h1:B33TUeIrHtJH2/Qj2bRdU+UZ1BvZwFyP55JWMxHirWg=
github.com/maxlerebourg/simpleredis v1.0.9/go.mod h1:/DH8zOK6kDskSqoX/m5CJJdNGfkIQZd/ERBJgytDDSk= github.com/maxlerebourg/simpleredis v1.0.11/go.mod h1:lT4LX02SOsE9PxUcSrz1QW5ZnO86gPbaiYBxmtcXEls=
+5 -4
View File
@@ -3,6 +3,7 @@
package cache package cache
import ( import (
"errors"
"fmt" "fmt"
ttl_map "github.com/leprosus/golang-ttl-map" 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 { if isCached && isValid && len(valueString) > 0 {
return valueString, nil return valueString, nil
} }
return "", fmt.Errorf(CacheMiss) return "", errors.New(CacheMiss)
} }
func (localCache) set(key, value string, duration int64) { func (localCache) set(key, value string, duration int64) {
@@ -60,20 +61,20 @@ func (redisCache) get(key string) (string, error) {
return valueString, nil return valueString, nil
} }
if err.Error() == simpleredis.RedisMiss { if err.Error() == simpleredis.RedisMiss {
return "", fmt.Errorf(CacheMiss) return "", errors.New(CacheMiss)
} }
return "", err return "", err
} }
func (rc redisCache) set(key, value string, duration int64) { func (rc redisCache) set(key, value string, duration int64) {
if err := redis.Set(key, []byte(value), duration); err != nil { 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) { func (rc redisCache) delete(key string) {
if err := redis.Del(key); err != nil { if err := redis.Del(key); err != nil {
rc.log.Error(fmt.Sprintf("cache:deleteDecisionRedisCache %s", err.Error())) rc.log.Error("cache:deleteDecisionRedisCache " + err.Error())
} }
} }
+8 -8
View File
@@ -48,7 +48,7 @@ var (
}, },
configuration.TurnstileProvider: { configuration.TurnstileProvider: {
js: "https://challenges.cloudflare.com/turnstile/v0/api.js", js: "https://challenges.cloudflare.com/turnstile/v0/api.js",
key: "cf-captcha", key: "cf-turnstile",
validate: "https://challenges.cloudflare.com/turnstile/v0/siteverify", 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) { func (c *Client) ServeHTTP(rw http.ResponseWriter, r *http.Request, remoteIP string) {
valid, err := c.Validate(r) valid, err := c.Validate(r)
if err != nil { 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) rw.WriteHeader(http.StatusBadRequest)
return return
} }
if valid { if valid {
c.log.Debug("captcha:ServeHTTP captcha: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) http.Redirect(rw, r, r.URL.String(), http.StatusFound)
return return
} }
@@ -94,13 +94,13 @@ func (c *Client) ServeHTTP(rw http.ResponseWriter, r *http.Request, remoteIP str
"FrontendKey": captcha[c.provider].key, "FrontendKey": captcha[c.provider].key,
}) })
if err != nil { 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. // Check Verify if the captcha is already done.
func (c *Client) Check(remoteIP string) bool { 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 passed := value == cache.CaptchaDoneValue
c.log.Debug(fmt.Sprintf("captcha:Check ip:%s pass:%v", remoteIP, passed)) c.log.Debug(fmt.Sprintf("captcha:Check ip:%s pass:%v", remoteIP, passed))
return passed return passed
@@ -113,10 +113,10 @@ type responseProvider struct {
// Validate Verify the captcha from provider API. // Validate Verify the captcha from provider API.
func (c *Client) Validate(r *http.Request) (bool, error) { func (c *Client) Validate(r *http.Request) (bool, error) {
if r.Method != http.MethodPost { 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 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 == "" { if response == "" {
c.log.Debug("captcha:Validate no captcha response found in request") c.log.Debug("captcha:Validate no captcha response found in request")
return false, nil return false, nil
@@ -130,7 +130,7 @@ func (c *Client) Validate(r *http.Request) (bool, error) {
} }
defer func() { defer func() {
if err = res.Body.Close(); err != nil { 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") { if !strings.Contains(res.Header.Get("content-type"), "application/json") {
+14 -11
View File
@@ -4,6 +4,7 @@ package configuration
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"errors"
"fmt" "fmt"
"html/template" "html/template"
"net/http" "net/http"
@@ -40,6 +41,7 @@ type Config struct {
CrowdsecAppsecEnabled bool `json:"crowdsecAppsecEnabled,omitempty"` CrowdsecAppsecEnabled bool `json:"crowdsecAppsecEnabled,omitempty"`
CrowdsecAppsecHost string `json:"crowdsecAppsecHost,omitempty"` CrowdsecAppsecHost string `json:"crowdsecAppsecHost,omitempty"`
CrowdsecAppsecFailureBlock bool `json:"crowdsecAppsecFailureBlock,omitempty"` CrowdsecAppsecFailureBlock bool `json:"crowdsecAppsecFailureBlock,omitempty"`
CrowdsecAppsecUnreachableBlock bool `json:"crowdsecAppsecUnreachableBlock,omitempty"`
CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"` CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"`
CrowdsecLapiHost string `json:"crowdsecLapiHost,omitempty"` CrowdsecLapiHost string `json:"crowdsecLapiHost,omitempty"`
CrowdsecLapiKey string `json:"crowdsecLapiKey,omitempty"` CrowdsecLapiKey string `json:"crowdsecLapiKey,omitempty"`
@@ -96,6 +98,7 @@ func New() *Config {
CrowdsecAppsecEnabled: false, CrowdsecAppsecEnabled: false,
CrowdsecAppsecHost: "crowdsec:7422", CrowdsecAppsecHost: "crowdsec:7422",
CrowdsecAppsecFailureBlock: true, CrowdsecAppsecFailureBlock: true,
CrowdsecAppsecUnreachableBlock: true,
CrowdsecLapiScheme: HTTP, CrowdsecLapiScheme: HTTP,
CrowdsecLapiHost: "crowdsec:8080", CrowdsecLapiHost: "crowdsec:8080",
CrowdsecLapiKey: "", CrowdsecLapiKey: "",
@@ -124,7 +127,7 @@ func New() *Config {
func GetVariable(config *Config, key string) (string, error) { func GetVariable(config *Config, key string) (string, error) {
value := "" value := ""
object := reflect.Indirect(reflect.ValueOf(config)) 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 // Here linter say you should simplify this code, but lets not, performance is important not clarity and complexity
fp := field.String() fp := field.String()
if fp != "" { if fp != "" {
@@ -151,7 +154,7 @@ func GetVariable(config *Config, key string) (string, error) {
func GetHTMLTemplate(path string) (*template.Template, error) { func GetHTMLTemplate(path string) (*template.Template, error) {
var err error var err error
if path == "" { if path == "" {
return nil, fmt.Errorf("no html template provided") return nil, errors.New("no html template provided")
} }
//nolint:gosec //nolint:gosec
b, err := os.ReadFile(path) 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 // We need to either have crowdsecLapiKey defined or the BouncerCert and Bouncerkey
if lapiKey == "" && (certBouncer == "" || certBouncerKey == "") { 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 == "") { } else if lapiKey != "" && (certBouncer == "" || certBouncerKey == "") {
lapiKey = strings.TrimSpace(lapiKey) lapiKey = strings.TrimSpace(lapiKey)
if err = validateParamsAPIKey(lapiKey); err != nil { 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 // See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
func validateParamsAPIKey(lapiKey string) error { func validateParamsAPIKey(lapiKey string) error {
reg := regexp.MustCompile("^[a-zA-Z0-9 !#$%&'*+-.^_`|~=/]*$") 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 fmt.Errorf("CrowdsecLapiKey doesn't valid this regexp: '/%s/'", reg.String())
} }
return nil return nil
@@ -279,12 +282,12 @@ func validateParamsTLS(config *Config) error {
return err return err
} }
if certAuth == "" { 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 := new(tls.Config)
tlsConfig.RootCAs = x509.NewCertPool() tlsConfig.RootCAs = x509.NewCertPool()
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuth)) { if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuth)) {
return fmt.Errorf("failed parsing pem file") return errors.New("failed parsing pem file")
} }
return nil return nil
} }
@@ -321,17 +324,17 @@ func validateParamsRequired(config *Config) error {
} }
} }
if config.UpdateMaxFailure < -1 { 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) { 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) { 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) { 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 return nil
} }
@@ -360,7 +363,7 @@ func GetTLSConfigCrowdsec(config *Config, log *logger.Log) (*tls.Config, error)
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuthority)) { if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(certAuthority)) {
// here we return because if CrowdsecLapiTLSInsecureVerify is false // here we return because if CrowdsecLapiTLSInsecureVerify is false
// and CA not load, we can't communicate with https // 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") log.Debug("getTLSConfigCrowdsec:CrowdsecLapiTLSCertificateAuthority CA added successfully")
} }
+2 -1
View File
@@ -3,6 +3,7 @@
package ip package ip
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "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. // Contains checks if provided address is in the trusted IPs.
func (ip *Checker) Contains(addr string) (bool, error) { func (ip *Checker) Contains(addr string) (bool, error) {
if len(addr) == 0 { if len(addr) == 0 {
return false, fmt.Errorf("Contains:noAddress") return false, errors.New("Contains:noAddress")
} }
ipAddr, err := parseIP(addr) ipAddr, err := parseIP(addr)
-1
View File
@@ -122,7 +122,6 @@ func (sr *SimpleRedis) askRedis(cmd redisCmd, channel chan redisCmd) redisCmd {
} }
read, _ = reader.ReadLineBytes() read, _ = reader.ReadLineBytes()
return redisCmd{Data: read} return redisCmd{Data: read}
} }
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
# github.com/leprosus/golang-ttl-map v1.1.7 # github.com/leprosus/golang-ttl-map v1.1.7
## explicit; go 1.15 ## explicit; go 1.15
github.com/leprosus/golang-ttl-map github.com/leprosus/golang-ttl-map
# github.com/maxlerebourg/simpleredis v1.0.9 # github.com/maxlerebourg/simpleredis v1.0.11
## explicit; go 1.19 ## explicit; go 1.22
github.com/maxlerebourg/simpleredis github.com/maxlerebourg/simpleredis