@@ -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 }
0 commit comments