Skip to content

Commit e1cc737

Browse files
Add IP remediation fallback when remediation variable not set (#86)
Add IP remediation fallback when crowdsec-ip message not received When HAProxy is behind an upstream proxy and only fires on-frontend-http-request, the crowdsec-ip message may not set the remediation variable. This adds fallback logic to check IP remediation directly in handleHTTPRequest when needed. Changes: - Extract IP checking logic into shared getIPRemediation() function (DRY) - Add checkIPRemediation() fallback in handleHTTPRequest - Include src-ip in crowdsec-http message args for fallback scenario - Refactor handleIPRequest to use shared getIPRemediation() function This enables configurations where crowdsec-http can work standalone with req.hdr_ip() to extract real client IP from proxy headers.
1 parent 8b2b251 commit e1cc737

File tree

2 files changed

+42
-17
lines changed

2 files changed

+42
-17
lines changed

config/crowdsec.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ spoe-agent crowdsec-agent
1212
log global
1313

1414
## This message is used to customise the remediation from crowdsec-ip based on the host header
15+
## src-ip is included as fallback in case crowdsec-ip message didn't fire
1516
spoe-message crowdsec-http
16-
args remediation=var(txn.crowdsec.remediation) crowdsec_captcha_cookie=req.cook(crowdsec_captcha_cookie) id=unique-id host=hdr(Host) method=method path=path query=query version=req.ver headers=req.hdrs body=req.body url=url ssl=ssl_fc
17+
args remediation=var(txn.crowdsec.remediation) crowdsec_captcha_cookie=req.cook(crowdsec_captcha_cookie) id=unique-id host=hdr(Host) method=method path=path query=query version=req.ver headers=req.hdrs body=req.body url=url ssl=ssl_fc src-ip=src src-port=src_port
1718
event on-frontend-http-request
1819

1920
## This message should be the first to trigger in the chain

pkg/spoa/root.go

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,10 @@ func (s *Spoa) handleHTTPRequest(req *request.Request, mes *message.Message) {
215215
log.Debug("remediation: ", *rstring)
216216
r = remediation.FromString(*rstring)
217217
} else {
218-
log.Info("ip remediation was not found in message, defaulting to allow")
218+
// IP remediation not found - fallback to checking IP directly
219+
// This handles cases where crowdsec-ip message didn't fire (e.g., on-client-session not triggered)
220+
log.Debug("ip remediation was not found in message, checking IP directly")
221+
s.checkIPRemediation(req, mes, &r)
219222
}
220223

221224
hoststring, err := readKeyFromMessage[string](mes, "host")
@@ -493,26 +496,16 @@ func (s *Spoa) handleHTTPRequest(req *request.Request, mes *message.Message) {
493496
// request.Header = headers
494497
}
495498

496-
// Handles checking the IP address against the dataset
497-
func (s *Spoa) handleIPRequest(req *request.Request, mes *message.Message) {
498-
var r remediation.Remediation
499-
500-
ipType, err := readKeyFromMessage[net.IP](mes, "src-ip")
501-
502-
if err != nil {
503-
log.Error(err)
504-
return
505-
}
506-
507-
ipStr := ipType.String()
508-
509-
r, err = s.workerClient.GetIP(ipStr)
499+
// getIPRemediation performs IP and geo/country remediation checks
500+
// Returns the final remediation after checking IP, geo, and country
501+
func (s *Spoa) getIPRemediation(req *request.Request, ipStr string) remediation.Remediation {
502+
r, err := s.workerClient.GetIP(ipStr)
510503
if err != nil {
511504
log.WithFields(log.Fields{
512505
"ip": ipStr,
513506
"error": err,
514507
}).Error("Failed to get IP remediation")
515-
r = remediation.Allow // Safe default
508+
return remediation.Allow // Safe default
516509
}
517510

518511
if r < remediation.Unknown {
@@ -537,9 +530,40 @@ func (s *Spoa) handleIPRequest(req *request.Request, mes *message.Message) {
537530
}
538531
}
539532

533+
return r
534+
}
535+
536+
// Handles checking the IP address against the dataset
537+
func (s *Spoa) handleIPRequest(req *request.Request, mes *message.Message) {
538+
ipType, err := readKeyFromMessage[net.IP](mes, "src-ip")
539+
if err != nil {
540+
log.Error(err)
541+
return
542+
}
543+
544+
ipStr := ipType.String()
545+
r := s.getIPRemediation(req, ipStr)
540546
req.Actions.SetVar(action.ScopeTransaction, "remediation", r.String())
541547
}
542548

549+
// checkIPRemediation extracts IP from the message and checks remediation
550+
// Used as fallback when crowdsec-ip message didn't set remediation variable
551+
func (s *Spoa) checkIPRemediation(req *request.Request, mes *message.Message, r *remediation.Remediation) {
552+
ipType, err := readKeyFromMessage[net.IP](mes, "src-ip")
553+
if err != nil {
554+
log.WithError(err).Debug("failed to extract src-ip from message for fallback check")
555+
return
556+
}
557+
558+
ipStr := ipType.String()
559+
*r = s.getIPRemediation(req, ipStr)
560+
561+
log.WithFields(log.Fields{
562+
"ip": ipStr,
563+
"remediation": r.String(),
564+
}).Debug("IP remediation checked via fallback")
565+
}
566+
543567
func handlerWrapper(s *Spoa) func(req *request.Request) {
544568
return func(req *request.Request) {
545569
s.HAWaitGroup.Add(1)

0 commit comments

Comments
 (0)