github.com/crowdsecurity/crowdsec@v1.6.1/pkg/csplugin/broker_test.go (about) 1 //go:build linux || freebsd || netbsd || openbsd || solaris || !windows 2 3 package csplugin 4 5 import ( 6 "bytes" 7 "encoding/json" 8 "io" 9 "os" 10 "testing" 11 "time" 12 13 log "github.com/sirupsen/logrus" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 "gopkg.in/tomb.v2" 17 "gopkg.in/yaml.v2" 18 19 "github.com/crowdsecurity/go-cs-lib/cstest" 20 21 "github.com/crowdsecurity/crowdsec/pkg/csconfig" 22 "github.com/crowdsecurity/crowdsec/pkg/models" 23 ) 24 25 func (s *PluginSuite) permissionSetter(perm os.FileMode) func(*testing.T) { 26 return func(t *testing.T) { 27 err := os.Chmod(s.pluginBinary, perm) 28 require.NoError(t, err, "chmod %s %s", perm, s.pluginBinary) 29 } 30 } 31 32 func (s *PluginSuite) readconfig() PluginConfig { 33 var config PluginConfig 34 35 t := s.T() 36 37 orig, err := os.ReadFile(s.pluginConfig) 38 require.NoError(t, err, "unable to read config file %s", s.pluginConfig) 39 40 err = yaml.Unmarshal(orig, &config) 41 require.NoError(t, err, "unable to unmarshal config file") 42 43 return config 44 } 45 46 func (s *PluginSuite) writeconfig(config PluginConfig) { 47 t := s.T() 48 data, err := yaml.Marshal(&config) 49 require.NoError(t, err, "unable to marshal config file") 50 51 err = os.WriteFile(s.pluginConfig, data, 0644) 52 require.NoError(t, err, "unable to write config file %s", s.pluginConfig) 53 } 54 55 func (s *PluginSuite) TestBrokerInit() { 56 tests := []struct { 57 name string 58 action func(*testing.T) 59 procCfg csconfig.PluginCfg 60 expectedErr string 61 }{ 62 { 63 name: "valid config", 64 }, 65 { 66 name: "group writable binary", 67 expectedErr: "notification-dummy is world writable", 68 action: s.permissionSetter(0o722), 69 }, 70 { 71 name: "group writable binary", 72 expectedErr: "notification-dummy is group writable", 73 action: s.permissionSetter(0o724), 74 }, 75 { 76 name: "no plugin dir", 77 expectedErr: cstest.FileNotFoundMessage, 78 action: func(t *testing.T) { 79 err := os.RemoveAll(s.runDir) 80 require.NoError(t, err) 81 }, 82 }, 83 { 84 name: "no plugin binary", 85 expectedErr: "binary for plugin dummy_default not found", 86 action: func(t *testing.T) { 87 err := os.Remove(s.pluginBinary) 88 require.NoError(t, err) 89 }, 90 }, 91 { 92 name: "only specify user", 93 expectedErr: "both plugin user and group must be set", 94 procCfg: csconfig.PluginCfg{ 95 User: "123445555551122toto", 96 }, 97 }, 98 { 99 name: "only specify group", 100 expectedErr: "both plugin user and group must be set", 101 procCfg: csconfig.PluginCfg{ 102 Group: "123445555551122toto", 103 }, 104 }, 105 { 106 name: "Fails to run as root", 107 expectedErr: "operation not permitted", 108 procCfg: csconfig.PluginCfg{ 109 User: "root", 110 Group: "root", 111 }, 112 }, 113 { 114 name: "Invalid user and group", 115 expectedErr: "toto1234", 116 procCfg: csconfig.PluginCfg{ 117 User: "toto1234", 118 Group: "toto1234", 119 }, 120 }, 121 { 122 name: "Valid user and invalid group", 123 expectedErr: "toto1234", 124 procCfg: csconfig.PluginCfg{ 125 User: "nobody", 126 Group: "toto1234", 127 }, 128 }, 129 } 130 131 for _, tc := range tests { 132 tc := tc 133 s.Run(tc.name, func() { 134 t := s.T() 135 if tc.action != nil { 136 tc.action(t) 137 } 138 _, err := s.InitBroker(&tc.procCfg) 139 cstest.RequireErrorContains(t, err, tc.expectedErr) 140 }) 141 } 142 } 143 144 func (s *PluginSuite) TestBrokerNoThreshold() { 145 var alerts []models.Alert 146 147 DefaultEmptyTicker = 50 * time.Millisecond 148 149 t := s.T() 150 151 pb, err := s.InitBroker(nil) 152 require.NoError(t, err) 153 154 tomb := tomb.Tomb{} 155 go pb.Run(&tomb) 156 157 // send one item, it should be processed right now 158 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 159 160 time.Sleep(200 * time.Millisecond) 161 162 // we expect one now 163 content, err := os.ReadFile("./out") 164 require.NoError(t, err, "Error reading file") 165 166 err = json.Unmarshal(content, &alerts) 167 require.NoError(t, err) 168 assert.Len(t, alerts, 1) 169 170 // remove it 171 os.Remove("./out") 172 173 // and another one 174 log.Printf("second send") 175 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 176 177 time.Sleep(200 * time.Millisecond) 178 179 // we expect one again, as we cleaned the file 180 content, err = os.ReadFile("./out") 181 require.NoError(t, err, "Error reading file") 182 183 err = json.Unmarshal(content, &alerts) 184 log.Printf("content-> %s", content) 185 require.NoError(t, err) 186 assert.Len(t, alerts, 1) 187 } 188 189 func (s *PluginSuite) TestBrokerRunGroupAndTimeThreshold_TimeFirst() { 190 // test grouping by "time" 191 DefaultEmptyTicker = 50 * time.Millisecond 192 193 t := s.T() 194 195 // set groupwait and groupthreshold, should honor whichever comes first 196 cfg := s.readconfig() 197 cfg.GroupThreshold = 4 198 cfg.GroupWait = 1 * time.Second 199 s.writeconfig(cfg) 200 201 pb, err := s.InitBroker(nil) 202 require.NoError(t, err) 203 204 tomb := tomb.Tomb{} 205 go pb.Run(&tomb) 206 207 // send data 208 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 209 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 210 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 211 212 time.Sleep(500 * time.Millisecond) 213 // because of group threshold, we shouldn't have data yet 214 assert.NoFileExists(t, "./out") 215 time.Sleep(1 * time.Second) 216 // after 1 seconds, we should have data 217 content, err := os.ReadFile("./out") 218 require.NoError(t, err) 219 220 var alerts []models.Alert 221 err = json.Unmarshal(content, &alerts) 222 require.NoError(t, err) 223 assert.Len(t, alerts, 3) 224 } 225 226 func (s *PluginSuite) TestBrokerRunGroupAndTimeThreshold_CountFirst() { 227 DefaultEmptyTicker = 50 * time.Millisecond 228 229 t := s.T() 230 231 // set groupwait and groupthreshold, should honor whichever comes first 232 cfg := s.readconfig() 233 cfg.GroupThreshold = 4 234 cfg.GroupWait = 4 * time.Second 235 s.writeconfig(cfg) 236 237 pb, err := s.InitBroker(nil) 238 require.NoError(t, err) 239 240 tomb := tomb.Tomb{} 241 go pb.Run(&tomb) 242 243 // send data 244 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 245 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 246 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 247 248 time.Sleep(100 * time.Millisecond) 249 250 // because of group threshold, we shouldn't have data yet 251 assert.NoFileExists(t, "./out") 252 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 253 254 time.Sleep(100 * time.Millisecond) 255 256 // and now we should 257 content, err := os.ReadFile("./out") 258 require.NoError(t, err, "Error reading file") 259 260 var alerts []models.Alert 261 err = json.Unmarshal(content, &alerts) 262 require.NoError(t, err) 263 assert.Len(t, alerts, 4) 264 } 265 266 func (s *PluginSuite) TestBrokerRunGroupThreshold() { 267 // test grouping by "size" 268 DefaultEmptyTicker = 50 * time.Millisecond 269 270 t := s.T() 271 272 // set groupwait 273 cfg := s.readconfig() 274 cfg.GroupThreshold = 4 275 s.writeconfig(cfg) 276 277 pb, err := s.InitBroker(nil) 278 require.NoError(t, err) 279 280 tomb := tomb.Tomb{} 281 go pb.Run(&tomb) 282 283 // send data 284 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 285 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 286 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 287 288 time.Sleep(time.Second) 289 290 // because of group threshold, we shouldn't have data yet 291 assert.NoFileExists(t, "./out") 292 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 293 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 294 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 295 296 time.Sleep(time.Second) 297 298 // and now we should 299 content, err := os.ReadFile("./out") 300 require.NoError(t, err, "Error reading file") 301 302 decoder := json.NewDecoder(bytes.NewReader(content)) 303 304 var alerts []models.Alert 305 306 // two notifications, one with 4 alerts, one with 2 alerts 307 308 err = decoder.Decode(&alerts) 309 require.NoError(t, err) 310 assert.Len(t, alerts, 4) 311 312 err = decoder.Decode(&alerts) 313 require.NoError(t, err) 314 assert.Len(t, alerts, 2) 315 316 err = decoder.Decode(&alerts) 317 assert.Equal(t, err, io.EOF) 318 } 319 320 func (s *PluginSuite) TestBrokerRunTimeThreshold() { 321 DefaultEmptyTicker = 50 * time.Millisecond 322 323 t := s.T() 324 325 // set groupwait 326 cfg := s.readconfig() 327 cfg.GroupWait = 1 * time.Second 328 s.writeconfig(cfg) 329 330 pb, err := s.InitBroker(nil) 331 require.NoError(t, err) 332 333 tomb := tomb.Tomb{} 334 go pb.Run(&tomb) 335 336 // send data 337 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 338 339 time.Sleep(200 * time.Millisecond) 340 341 // we shouldn't have data yet 342 assert.NoFileExists(t, "./out") 343 time.Sleep(1 * time.Second) 344 345 // and now we should 346 content, err := os.ReadFile("./out") 347 require.NoError(t, err, "Error reading file") 348 349 var alerts []models.Alert 350 err = json.Unmarshal(content, &alerts) 351 require.NoError(t, err) 352 assert.Len(t, alerts, 1) 353 } 354 355 func (s *PluginSuite) TestBrokerRunSimple() { 356 DefaultEmptyTicker = 50 * time.Millisecond 357 358 t := s.T() 359 360 pb, err := s.InitBroker(nil) 361 require.NoError(t, err) 362 363 tomb := tomb.Tomb{} 364 go pb.Run(&tomb) 365 366 assert.NoFileExists(t, "./out") 367 368 defer os.Remove("./out") 369 370 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 371 pb.PluginChannel <- ProfileAlert{ProfileID: uint(0), Alert: &models.Alert{}} 372 // make it wait a bit, CI can be slow 373 time.Sleep(time.Second) 374 375 content, err := os.ReadFile("./out") 376 require.NoError(t, err, "Error reading file") 377 378 decoder := json.NewDecoder(bytes.NewReader(content)) 379 380 var alerts []models.Alert 381 382 // two notifications, one alert each 383 384 err = decoder.Decode(&alerts) 385 require.NoError(t, err) 386 assert.Len(t, alerts, 1) 387 388 err = decoder.Decode(&alerts) 389 require.NoError(t, err) 390 assert.Len(t, alerts, 1) 391 392 err = decoder.Decode(&alerts) 393 assert.Equal(t, err, io.EOF) 394 }