github.com/crowdsecurity/crowdsec@v1.6.1/pkg/exprhelpers/crowdsec_cti_test.go (about) 1 package exprhelpers 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "io" 8 "net/http" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/crowdsecurity/go-cs-lib/ptr" 17 18 "github.com/crowdsecurity/crowdsec/pkg/cticlient" 19 ) 20 21 var sampledata = map[string]cticlient.SmokeItem{ 22 //1.2.3.4 is a known false positive 23 "1.2.3.4": { 24 Ip: "1.2.3.4", 25 Classifications: cticlient.CTIClassifications{ 26 FalsePositives: []cticlient.CTIClassification{ 27 { 28 Name: "example_false_positive", 29 Label: "Example False Positive", 30 }, 31 }, 32 }, 33 }, 34 //1.2.3.5 is a known bad-guy, and part of FIRE 35 "1.2.3.5": { 36 Ip: "1.2.3.5", 37 Classifications: cticlient.CTIClassifications{ 38 Classifications: []cticlient.CTIClassification{ 39 { 40 Name: "community-blocklist", 41 Label: "CrowdSec Community Blocklist", 42 Description: "IP belong to the CrowdSec Community Blocklist", 43 }, 44 }, 45 }, 46 }, 47 //1.2.3.6 is a bad guy (high bg noise), but not in FIRE 48 "1.2.3.6": { 49 Ip: "1.2.3.6", 50 BackgroundNoiseScore: new(int), 51 Behaviors: []*cticlient.CTIBehavior{ 52 {Name: "ssh:bruteforce", Label: "SSH Bruteforce", Description: "SSH Bruteforce"}, 53 }, 54 AttackDetails: []*cticlient.CTIAttackDetails{ 55 {Name: "crowdsecurity/ssh-bf", Label: "Example Attack"}, 56 {Name: "crowdsecurity/ssh-slow-bf", Label: "Example Attack"}, 57 }, 58 }, 59 //1.2.3.7 is a ok guy, but part of a bad range 60 "1.2.3.7": {}, 61 } 62 63 const validApiKey = "my-api-key" 64 65 type RoundTripFunc func(req *http.Request) *http.Response 66 67 func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 68 return f(req), nil 69 } 70 71 func smokeHandler(req *http.Request) *http.Response { 72 apiKey := req.Header.Get("x-api-key") 73 if apiKey != validApiKey { 74 return &http.Response{ 75 StatusCode: http.StatusForbidden, 76 Body: nil, 77 Header: make(http.Header), 78 } 79 } 80 81 requestedIP := strings.Split(req.URL.Path, "/")[3] 82 83 sample, ok := sampledata[requestedIP] 84 if !ok { 85 return &http.Response{ 86 StatusCode: http.StatusNotFound, 87 Body: nil, 88 Header: make(http.Header), 89 } 90 } 91 92 body, err := json.Marshal(sample) 93 if err != nil { 94 return &http.Response{ 95 StatusCode: http.StatusInternalServerError, 96 Body: nil, 97 Header: make(http.Header), 98 } 99 } 100 101 reader := io.NopCloser(bytes.NewReader(body)) 102 103 return &http.Response{ 104 StatusCode: http.StatusOK, 105 // Send response to be tested 106 Body: reader, 107 Header: make(http.Header), 108 ContentLength: 0, 109 } 110 } 111 112 func TestNillClient(t *testing.T) { 113 defer ShutdownCrowdsecCTI() 114 115 if err := InitCrowdsecCTI(ptr.Of(""), nil, nil, nil); !errors.Is(err, cticlient.ErrDisabled) { 116 t.Fatalf("failed to init CTI : %s", err) 117 } 118 119 item, err := CrowdsecCTI("1.2.3.4") 120 assert.Equal(t, err, cticlient.ErrDisabled) 121 assert.Equal(t, &cticlient.SmokeItem{}, item) 122 } 123 124 func TestInvalidAuth(t *testing.T) { 125 defer ShutdownCrowdsecCTI() 126 127 if err := InitCrowdsecCTI(ptr.Of("asdasd"), nil, nil, nil); err != nil { 128 t.Fatalf("failed to init CTI : %s", err) 129 } 130 //Replace the client created by InitCrowdsecCTI with one that uses a custom transport 131 ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey("asdasd"), cticlient.WithHTTPClient(&http.Client{ 132 Transport: RoundTripFunc(smokeHandler), 133 })) 134 135 item, err := CrowdsecCTI("1.2.3.4") 136 assert.Equal(t, &cticlient.SmokeItem{}, item) 137 assert.False(t, CTIApiEnabled) 138 assert.Equal(t, err, cticlient.ErrUnauthorized) 139 140 //CTI is now disabled, all requests should return empty 141 ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey(validApiKey), cticlient.WithHTTPClient(&http.Client{ 142 Transport: RoundTripFunc(smokeHandler), 143 })) 144 145 item, err = CrowdsecCTI("1.2.3.4") 146 assert.Equal(t, &cticlient.SmokeItem{}, item) 147 assert.False(t, CTIApiEnabled) 148 assert.Equal(t, err, cticlient.ErrDisabled) 149 } 150 151 func TestNoKey(t *testing.T) { 152 defer ShutdownCrowdsecCTI() 153 154 err := InitCrowdsecCTI(nil, nil, nil, nil) 155 require.ErrorIs(t, err, cticlient.ErrDisabled) 156 //Replace the client created by InitCrowdsecCTI with one that uses a custom transport 157 ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey("asdasd"), cticlient.WithHTTPClient(&http.Client{ 158 Transport: RoundTripFunc(smokeHandler), 159 })) 160 161 item, err := CrowdsecCTI("1.2.3.4") 162 assert.Equal(t, &cticlient.SmokeItem{}, item) 163 assert.False(t, CTIApiEnabled) 164 assert.Equal(t, err, cticlient.ErrDisabled) 165 } 166 167 func TestCache(t *testing.T) { 168 defer ShutdownCrowdsecCTI() 169 170 cacheDuration := 1 * time.Second 171 if err := InitCrowdsecCTI(ptr.Of(validApiKey), &cacheDuration, nil, nil); err != nil { 172 t.Fatalf("failed to init CTI : %s", err) 173 } 174 //Replace the client created by InitCrowdsecCTI with one that uses a custom transport 175 ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey(validApiKey), cticlient.WithHTTPClient(&http.Client{ 176 Transport: RoundTripFunc(smokeHandler), 177 })) 178 179 item, err := CrowdsecCTI("1.2.3.4") 180 ctiResp := item.(*cticlient.SmokeItem) 181 assert.Equal(t, "1.2.3.4", ctiResp.Ip) 182 assert.True(t, CTIApiEnabled) 183 assert.Equal(t, 1, CTICache.Len(true)) 184 require.NoError(t, err) 185 186 item, err = CrowdsecCTI("1.2.3.4") 187 ctiResp = item.(*cticlient.SmokeItem) 188 189 assert.Equal(t, "1.2.3.4", ctiResp.Ip) 190 assert.True(t, CTIApiEnabled) 191 assert.Equal(t, 1, CTICache.Len(true)) 192 require.NoError(t, err) 193 194 time.Sleep(2 * time.Second) 195 196 assert.Equal(t, 0, CTICache.Len(true)) 197 198 item, err = CrowdsecCTI("1.2.3.4") 199 ctiResp = item.(*cticlient.SmokeItem) 200 201 assert.Equal(t, "1.2.3.4", ctiResp.Ip) 202 assert.True(t, CTIApiEnabled) 203 assert.Equal(t, 1, CTICache.Len(true)) 204 require.NoError(t, err) 205 }