Fix CrowdSec review findings: observe metadata, idempotent Start, HasRestrictions, proto map alias

This commit is contained in:
Viktor Liu
2026-03-29 08:50:34 +02:00
parent ae84272a30
commit 6411136fec
6 changed files with 25 additions and 5 deletions

View File

@@ -1,6 +1,7 @@
package accesslogs
import (
"maps"
"net"
"net/netip"
"time"
@@ -57,7 +58,7 @@ func (a *AccessLogEntry) FromProto(serviceLog *proto.AccessLog) {
a.BytesDownload = serviceLog.GetBytesDownload()
a.Protocol = AccessLogProtocol(serviceLog.GetProtocol())
if m := serviceLog.GetMetadata(); len(m) > 0 {
a.Metadata = m
a.Metadata = maps.Clone(m)
}
if sourceIP := serviceLog.GetSourceIp(); sourceIP != "" {

View File

@@ -139,8 +139,8 @@ func (m Manager) ClusterRequireSubdomain(ctx context.Context, clusterAddr string
return m.store.GetClusterRequireSubdomain(ctx, clusterAddr)
}
// ClusterSupportsCrowdSec returns whether any active proxy in the cluster
// has CrowdSec configured. Returns nil when no proxy has reported capabilities.
// ClusterSupportsCrowdSec returns whether all active proxies in the cluster
// have CrowdSec configured (unanimous). Returns nil when no proxy has reported capabilities.
func (m Manager) ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool {
return m.store.GetClusterSupportsCrowdSec(ctx, clusterAddr)
}

View File

@@ -170,6 +170,9 @@ func (mw *Middleware) checkIPRestrictions(w http.ResponseWriter, r *http.Request
if verdict.IsCrowdSec() {
if cd := proxy.CapturedDataFromContext(r.Context()); cd != nil {
cd.SetMetadata("crowdsec_verdict", verdict.String())
if config.IPRestrictions.IsObserveOnly(verdict) {
cd.SetMetadata("crowdsec_mode", "observe")
}
}
}

View File

@@ -4,6 +4,7 @@ package crowdsec
import (
"context"
"errors"
"net/netip"
"strings"
"sync"
@@ -83,6 +84,11 @@ func (b *Bouncer) Start(ctx context.Context) error {
done := make(chan struct{})
b.lifeMu.Lock()
if b.cancel != nil {
b.lifeMu.Unlock()
cancel()
return errors.New("bouncer already started")
}
b.cancel = cancel
b.done = done
b.lifeMu.Unlock()

View File

@@ -174,7 +174,12 @@ func (v Verdict) String() string {
// IsCrowdSec returns true when the verdict originates from a CrowdSec check.
func (v Verdict) IsCrowdSec() bool {
return v >= DenyCrowdSecBan && v <= DenyCrowdSecUnavailable
switch v {
case DenyCrowdSecBan, DenyCrowdSecCaptcha, DenyCrowdSecThrottle, DenyCrowdSecUnavailable:
return true
default:
return false
}
}
// IsObserveOnly returns true when v is a CrowdSec verdict and the filter is in
@@ -306,5 +311,5 @@ func (f *Filter) HasRestrictions() bool {
}
return len(f.AllowedCIDRs) > 0 || len(f.BlockedCIDRs) > 0 ||
len(f.AllowedCountries) > 0 || len(f.BlockedCountries) > 0 ||
(f.CrowdSec != nil && f.CrowdSecMode != CrowdSecOff)
f.CrowdSecMode == CrowdSecEnforce || f.CrowdSecMode == CrowdSecObserve
}

View File

@@ -403,4 +403,9 @@ func TestFilter_HasRestrictions_CrowdSec(t *testing.T) {
cs := &mockCrowdSec{ready: true}
f := ParseFilter(FilterConfig{CrowdSec: cs, CrowdSecMode: CrowdSecEnforce})
assert.True(t, f.HasRestrictions())
// Enforce mode without checker (LAPI not configured): still has restrictions
// because Check() will fail-closed with DenyCrowdSecUnavailable.
f2 := ParseFilter(FilterConfig{CrowdSec: nil, CrowdSecMode: CrowdSecEnforce})
assert.True(t, f2.HasRestrictions())
}