Disable request blocking when CrowdSec is unavailable in live mode. (#373)

* Disable request blocking when CrowdSec is unavailable in live mode.

* 📝 document the live-mode behaviour and cover it with tests

The README said UpdateMaxFailure is "used only in stream and alone mode",
which this branch makes untrue, so the feature was undiscoverable from the
docs. It now also records that -1 is the only value with any effect in live
and none mode, since that path has no failure counter.

Test_handleNoStreamCache had a TODO and no cases, so this path had no
coverage in either direction. Adds four: live blocks at the default and at
any positive value, live lets the request through at -1, and none does the
same. The last two fail without the three-line change in this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: mhx <mathieu@hanotaux.fr>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-09-07 09:27:24 +02:00
1 parent 3314c3afa7
commit 73361a635e
3 files changed
+59 -9

No files matched your search

+2 -1
View File
@@ -473,7 +473,8 @@ make run
- UpdateMaxFailure
- int64
- default: 0
- Used only in `stream` and `alone` mode, the maximum number of time we can not reach Crowdsec before blocking traffic (set -1 to never block)
- In `stream` and `alone` mode, the maximum number of time we can not reach Crowdsec before blocking traffic (set -1 to never block)
- In `live` and `none` mode, only `-1` has an effect: a request is let through instead of blocked when Crowdsec cannot be reached. Any other value blocks on the first failure, there is no failure counter on that path.
- StreamStartupBlock
- bool
- default: true
+3
View File
@@ -546,6 +546,9 @@ func handleNoStreamCache(bouncer *Bouncer, remoteIP string) (string, error) {
}
body, err := crowdsecQuery(bouncer, routeURL.String(), nil)
if err != nil {
if bouncer.updateMaxFailure == -1 {
return cache.NoBannedValue, err
}
return cache.BannedValue, err
}
+54 -8
View File
@@ -123,22 +123,68 @@ func TestBouncer_ServeHTTP(t *testing.T) {
}
func Test_handleNoStreamCache(t *testing.T) {
type args struct {
bouncer *Bouncer
remoteIP string
newBouncer := func(mode string, updateMaxFailure int64) *Bouncer {
log := logger.New("ERROR", "")
cacheClient := &cache.Client{}
cacheClient.New(log, false, "", nil, "", "")
return &Bouncer{
crowdsecScheme: "http",
crowdsecHost: "127.0.0.1:1",
crowdsecPath: "/",
crowdsecMode: mode,
updateMaxFailure: updateMaxFailure,
defaultDecisionTimeout: 60,
httpClient: &http.Client{Timeout: time.Second},
cacheClient: cacheClient,
log: log,
}
}
tests := []struct {
name string
args args
wantErr bool
name string
mode string
updateMaxFailure int64
want string
wantErr bool
}{
// TODO: Add test cases.
{
name: "live mode blocks by default when crowdsec is unreachable",
mode: configuration.LiveMode,
updateMaxFailure: 0,
want: cache.BannedValue,
wantErr: true,
},
{
name: "live mode blocks for any positive updateMaxFailure",
mode: configuration.LiveMode,
updateMaxFailure: 3,
want: cache.BannedValue,
wantErr: true,
},
{
name: "live mode lets the request through when updateMaxFailure is -1",
mode: configuration.LiveMode,
updateMaxFailure: -1,
want: cache.NoBannedValue,
wantErr: true,
},
{
name: "none mode lets the request through when updateMaxFailure is -1",
mode: configuration.NoneMode,
updateMaxFailure: -1,
want: cache.NoBannedValue,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if _, err := handleNoStreamCache(tt.args.bouncer, tt.args.remoteIP); (err != nil) != tt.wantErr {
got, err := handleNoStreamCache(newBouncer(tt.mode, tt.updateMaxFailure), "1.2.3.4")
if (err != nil) != tt.wantErr {
t.Errorf("handleNoStreamCache() error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.want {
t.Errorf("handleNoStreamCache() = %q, want %q", got, tt.want)
}
})
}
}