Skip to content

Commit 904092a

Browse files
Merge pull request #64 from strausmann/fix/bouncer-status-revoked-field
fix: derive bouncer Valid status from revoked field (CrowdSec >= v1.7.x)
2 parents ce713c2 + 4f35fc3 commit 904092a

4 files changed

Lines changed: 50 additions & 16 deletions

File tree

internal/api/handlers/health_diagnostics.go

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,30 +31,49 @@ func parseBouncersJSON(bouncerOutput string, computeStatus bool) ([]models.Bounc
3131
if ipAddr, err := jsonparser.GetString(value, "ip_address"); err == nil {
3232
bouncer.IPAddress = ipAddr
3333
}
34-
if valid, err := jsonparser.GetBoolean(value, "valid"); err == nil {
35-
bouncer.Valid = valid
36-
}
37-
if lastPull, err := jsonparser.GetString(value, "last_pull"); err == nil {
38-
for _, layout := range []string{time.RFC3339Nano, time.RFC3339} {
39-
if t, err := time.Parse(layout, lastPull); err == nil {
40-
bouncer.LastPull = t
41-
break
34+
// Parse timestamps with multiple format support
35+
parseTime := func(key string) time.Time {
36+
if s, err := jsonparser.GetString(value, key); err == nil {
37+
for _, layout := range []string{time.RFC3339Nano, time.RFC3339} {
38+
if t, err := time.Parse(layout, s); err == nil {
39+
return t
40+
}
4241
}
4342
}
43+
return time.Time{}
4444
}
45+
46+
bouncer.LastPull = parseTime("last_pull")
47+
bouncer.CreatedAt = parseTime("created_at")
48+
bouncer.UpdatedAt = parseTime("updated_at")
49+
4550
if bouncerType, err := jsonparser.GetString(value, "type"); err == nil {
4651
bouncer.Type = bouncerType
4752
}
4853
if version, err := jsonparser.GetString(value, "version"); err == nil {
4954
bouncer.Version = version
5055
}
5156

57+
// A bouncer that exists in the list is valid by definition.
58+
// CrowdSec deletes revoked bouncers rather than marking them,
59+
// so the "revoked" and legacy "valid" fields are not reliable
60+
// indicators of actual connectivity (confirmed by CrowdSec team, see #47).
61+
bouncer.Valid = true
62+
5263
if computeStatus {
53-
if !bouncer.Valid {
54-
bouncer.Status = "disconnected"
55-
} else if bouncer.LastPull.IsZero() {
56-
bouncer.Status = "pending" // valid key, never pulled yet
57-
} else if time.Since(bouncer.LastPull) <= 60*time.Minute {
64+
// Determine last activity: prefer last_pull, fall back to updated_at
65+
// if the bouncer has been active since registration (updated_at > created_at + 5s).
66+
lastActivity := bouncer.LastPull
67+
if lastActivity.IsZero() &&
68+
bouncer.UpdatedAt.After(bouncer.CreatedAt.Add(5*time.Second)) {
69+
lastActivity = bouncer.UpdatedAt
70+
}
71+
72+
if !lastActivity.IsZero() && time.Since(lastActivity) <= 5*time.Minute {
73+
bouncer.Status = "connected"
74+
} else if lastActivity.IsZero() {
75+
bouncer.Status = "registered" // enrolled but never pulled
76+
} else if time.Since(lastActivity) <= 60*time.Minute {
5877
bouncer.Status = "connected"
5978
} else {
6079
bouncer.Status = "stale"
@@ -99,7 +118,12 @@ func checkBouncersHealth(dockerClient *docker.Client, containerName string) mode
99118

100119
activeBouncers := 0
101120
for _, b := range bouncers {
102-
if b.Valid && time.Since(b.LastPull) <= 60*time.Minute {
121+
lastActivity := b.LastPull
122+
if lastActivity.IsZero() &&
123+
b.UpdatedAt.After(b.CreatedAt.Add(5*time.Second)) {
124+
lastActivity = b.UpdatedAt
125+
}
126+
if !lastActivity.IsZero() && time.Since(lastActivity) <= 60*time.Minute {
103127
activeBouncers++
104128
}
105129
}

internal/models/models.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ type Bouncer struct {
151151
IPAddress string `json:"ip_address"`
152152
Valid bool `json:"valid"`
153153
LastPull time.Time `json:"last_pull"`
154+
CreatedAt time.Time `json:"created_at"`
155+
UpdatedAt time.Time `json:"updated_at"`
154156
Type string `json:"type"`
155157
Version string `json:"version"`
156158
Status string `json:"status"`

web/src/components/health/BouncerStatusMonitor.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ function BouncerStatusMonitor({ bouncers, className }: BouncerStatusMonitorProps
5151
<TableCell>{bouncer.type || '-'}</TableCell>
5252
<TableCell>{bouncer.version || '-'}</TableCell>
5353
<TableCell>
54-
<Badge variant={bouncer.valid ? 'success' : 'destructive'}>
55-
{bouncer.valid ? 'Valid' : 'Invalid'}
54+
<Badge variant={
55+
bouncer.status === 'connected' ? 'success' :
56+
bouncer.status === 'stale' ? 'warning' :
57+
bouncer.status === 'registered' ? 'outline' :
58+
bouncer.status === 'disconnected' ? 'destructive' :
59+
bouncer.valid ? 'success' : 'destructive'
60+
}>
61+
{bouncer.status || (bouncer.valid ? 'Valid' : 'Invalid')}
5662
</Badge>
5763
</TableCell>
5864
<TableCell className="text-sm text-muted-foreground">

web/src/pages/Bouncers.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export default function Bouncers() {
124124
return <Badge variant="warning">Stale</Badge>
125125
case 'pending':
126126
return <Badge variant="warning">Pending</Badge>
127+
case 'registered':
128+
return <Badge variant="outline">Registered</Badge>
127129
default:
128130
return <Badge variant="secondary">{status || 'Unknown'}</Badge>
129131
}

0 commit comments

Comments
 (0)