github.com/crowdsecurity/crowdsec@v1.6.1/pkg/exprhelpers/crowdsec_cti.go (about) 1 package exprhelpers 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/bluele/gcache" 8 "github.com/crowdsecurity/crowdsec/pkg/cticlient" 9 "github.com/crowdsecurity/crowdsec/pkg/types" 10 "github.com/pkg/errors" 11 log "github.com/sirupsen/logrus" 12 ) 13 14 var CTIUrl = "https://cti.api.crowdsec.net" 15 var CTIUrlSuffix = "/v2/smoke/" 16 var CTIApiKey = "" 17 18 // this is set for non-recoverable errors, such as 403 when querying API or empty API key 19 var CTIApiEnabled = false 20 21 // when hitting quotas or auth errors, we temporarily disable the API 22 var CTIBackOffUntil time.Time 23 var CTIBackOffDuration time.Duration = 5 * time.Minute 24 25 var ctiClient *cticlient.CrowdsecCTIClient 26 27 func InitCrowdsecCTI(Key *string, TTL *time.Duration, Size *int, LogLevel *log.Level) error { 28 if Key == nil || *Key == "" { 29 log.Warningf("CTI API key not set or empty, CTI will not be available") 30 return cticlient.ErrDisabled 31 } 32 CTIApiKey = *Key 33 if Size == nil { 34 Size = new(int) 35 *Size = 1000 36 } 37 if TTL == nil { 38 TTL = new(time.Duration) 39 *TTL = 5 * time.Minute 40 } 41 clog := log.New() 42 if err := types.ConfigureLogger(clog); err != nil { 43 return errors.Wrap(err, "while configuring datasource logger") 44 } 45 if LogLevel != nil { 46 clog.SetLevel(*LogLevel) 47 } 48 customLog := log.Fields{ 49 "type": "crowdsec-cti", 50 } 51 subLogger := clog.WithFields(customLog) 52 CrowdsecCTIInitCache(*Size, *TTL) 53 ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey(CTIApiKey), cticlient.WithLogger(subLogger)) 54 CTIApiEnabled = true 55 return nil 56 } 57 58 func ShutdownCrowdsecCTI() { 59 if CTICache != nil { 60 CTICache.Purge() 61 } 62 CTIApiKey = "" 63 CTIApiEnabled = false 64 } 65 66 // Cache for responses 67 var CTICache gcache.Cache 68 var CacheExpiration time.Duration 69 70 func CrowdsecCTIInitCache(size int, ttl time.Duration) { 71 CTICache = gcache.New(size).LRU().Build() 72 CacheExpiration = ttl 73 } 74 75 // func CrowdsecCTI(ip string) (*cticlient.SmokeItem, error) { 76 func CrowdsecCTI(params ...any) (any, error) { 77 var ip string 78 if !CTIApiEnabled { 79 return &cticlient.SmokeItem{}, cticlient.ErrDisabled 80 } 81 var ok bool 82 if ip, ok = params[0].(string); !ok { 83 return &cticlient.SmokeItem{}, fmt.Errorf("invalid type for ip : %T", params[0]) 84 } 85 86 if val, err := CTICache.Get(ip); err == nil && val != nil { 87 ctiClient.Logger.Debugf("cti cache fetch for %s", ip) 88 ret, ok := val.(*cticlient.SmokeItem) 89 if !ok { 90 ctiClient.Logger.Warningf("CrowdsecCTI: invalid type in cache, removing") 91 CTICache.Remove(ip) 92 } else { 93 return ret, nil 94 } 95 } 96 97 if !CTIBackOffUntil.IsZero() && time.Now().Before(CTIBackOffUntil) { 98 //ctiClient.Logger.Warningf("Crowdsec CTI client is in backoff mode, ending in %s", time.Until(CTIBackOffUntil)) 99 return &cticlient.SmokeItem{}, cticlient.ErrLimit 100 } 101 102 ctiClient.Logger.Infof("cti call for %s", ip) 103 before := time.Now() 104 ctiResp, err := ctiClient.GetIPInfo(ip) 105 ctiClient.Logger.Debugf("request for %s took %v", ip, time.Since(before)) 106 if err != nil { 107 switch { 108 case errors.Is(err, cticlient.ErrUnauthorized): 109 CTIApiEnabled = false 110 ctiClient.Logger.Errorf("Invalid API key provided, disabling CTI API") 111 return &cticlient.SmokeItem{}, cticlient.ErrUnauthorized 112 case errors.Is(err, cticlient.ErrLimit): 113 CTIBackOffUntil = time.Now().Add(CTIBackOffDuration) 114 ctiClient.Logger.Errorf("CTI API is throttled, will try again in %s", CTIBackOffDuration) 115 return &cticlient.SmokeItem{}, cticlient.ErrLimit 116 default: 117 ctiClient.Logger.Warnf("CTI API error : %s", err) 118 return &cticlient.SmokeItem{}, fmt.Errorf("unexpected error : %v", err) 119 } 120 } 121 122 if err := CTICache.SetWithExpire(ip, ctiResp, CacheExpiration); err != nil { 123 ctiClient.Logger.Warningf("IpCTI : error while caching CTI : %s", err) 124 return &cticlient.SmokeItem{}, cticlient.ErrUnknown 125 } 126 127 ctiClient.Logger.Tracef("CTI response : %v", *ctiResp) 128 129 return ctiResp, nil 130 }