apic_test.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221
  1. package apiserver
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "net"
  8. "net/http"
  9. "net/url"
  10. "os"
  11. "reflect"
  12. "sync"
  13. "testing"
  14. "time"
  15. "github.com/jarcoal/httpmock"
  16. "github.com/sirupsen/logrus"
  17. "github.com/stretchr/testify/assert"
  18. "github.com/stretchr/testify/require"
  19. "gopkg.in/tomb.v2"
  20. "github.com/crowdsecurity/go-cs-lib/pkg/cstest"
  21. "github.com/crowdsecurity/go-cs-lib/pkg/ptr"
  22. "github.com/crowdsecurity/go-cs-lib/pkg/version"
  23. "github.com/crowdsecurity/crowdsec/pkg/apiclient"
  24. "github.com/crowdsecurity/crowdsec/pkg/csconfig"
  25. "github.com/crowdsecurity/crowdsec/pkg/database"
  26. "github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
  27. "github.com/crowdsecurity/crowdsec/pkg/database/ent/machine"
  28. "github.com/crowdsecurity/crowdsec/pkg/models"
  29. "github.com/crowdsecurity/crowdsec/pkg/modelscapi"
  30. "github.com/crowdsecurity/crowdsec/pkg/types"
  31. )
  32. func getDBClient(t *testing.T) *database.Client {
  33. t.Helper()
  34. dbPath, err := os.CreateTemp("", "*sqlite")
  35. require.NoError(t, err)
  36. dbClient, err := database.NewClient(&csconfig.DatabaseCfg{
  37. Type: "sqlite",
  38. DbName: "crowdsec",
  39. DbPath: dbPath.Name(),
  40. })
  41. require.NoError(t, err)
  42. return dbClient
  43. }
  44. func getAPIC(t *testing.T) *apic {
  45. t.Helper()
  46. dbClient := getDBClient(t)
  47. return &apic{
  48. AlertsAddChan: make(chan []*models.Alert),
  49. //DecisionDeleteChan: make(chan []*models.Decision),
  50. dbClient: dbClient,
  51. mu: sync.Mutex{},
  52. startup: true,
  53. pullTomb: tomb.Tomb{},
  54. pushTomb: tomb.Tomb{},
  55. metricsTomb: tomb.Tomb{},
  56. scenarioList: make([]string, 0),
  57. consoleConfig: &csconfig.ConsoleConfig{
  58. ShareManualDecisions: ptr.Of(false),
  59. ShareTaintedScenarios: ptr.Of(false),
  60. ShareCustomScenarios: ptr.Of(false),
  61. ShareContext: ptr.Of(false),
  62. },
  63. isPulling: make(chan bool, 1),
  64. }
  65. }
  66. func absDiff(a int, b int) (c int) {
  67. if c = a - b; c < 0 {
  68. return -1 * c
  69. }
  70. return c
  71. }
  72. func assertTotalDecisionCount(t *testing.T, dbClient *database.Client, count int) {
  73. d := dbClient.Ent.Decision.Query().AllX(context.Background())
  74. assert.Len(t, d, count)
  75. }
  76. func assertTotalValidDecisionCount(t *testing.T, dbClient *database.Client, count int) {
  77. d := dbClient.Ent.Decision.Query().Where(
  78. decision.UntilGT(time.Now()),
  79. ).AllX(context.Background())
  80. assert.Len(t, d, count)
  81. }
  82. func jsonMarshalX(v interface{}) []byte {
  83. data, err := json.Marshal(v)
  84. if err != nil {
  85. panic(err)
  86. }
  87. return data
  88. }
  89. func assertTotalAlertCount(t *testing.T, dbClient *database.Client, count int) {
  90. d := dbClient.Ent.Alert.Query().AllX(context.Background())
  91. assert.Len(t, d, count)
  92. }
  93. func TestAPICCAPIPullIsOld(t *testing.T) {
  94. api := getAPIC(t)
  95. isOld, err := api.CAPIPullIsOld()
  96. require.NoError(t, err)
  97. assert.True(t, isOld)
  98. decision := api.dbClient.Ent.Decision.Create().
  99. SetUntil(time.Now().Add(time.Hour)).
  100. SetScenario("crowdsec/test").
  101. SetType("IP").
  102. SetScope("Country").
  103. SetValue("Blah").
  104. SetOrigin(types.CAPIOrigin).
  105. SaveX(context.Background())
  106. api.dbClient.Ent.Alert.Create().
  107. SetCreatedAt(time.Now()).
  108. SetScenario("crowdsec/test").
  109. AddDecisions(
  110. decision,
  111. ).
  112. SaveX(context.Background())
  113. isOld, err = api.CAPIPullIsOld()
  114. require.NoError(t, err)
  115. assert.False(t, isOld)
  116. }
  117. func TestAPICFetchScenariosListFromDB(t *testing.T) {
  118. tests := []struct {
  119. name string
  120. machineIDsWithScenarios map[string]string
  121. expectedScenarios []string
  122. }{
  123. {
  124. name: "Simple one machine with two scenarios",
  125. machineIDsWithScenarios: map[string]string{
  126. "a": "crowdsecurity/http-bf,crowdsecurity/ssh-bf",
  127. },
  128. expectedScenarios: []string{"crowdsecurity/ssh-bf", "crowdsecurity/http-bf"},
  129. },
  130. {
  131. name: "Multi machine with custom+hub scenarios",
  132. machineIDsWithScenarios: map[string]string{
  133. "a": "crowdsecurity/http-bf,crowdsecurity/ssh-bf,my_scenario",
  134. "b": "crowdsecurity/http-bf,crowdsecurity/ssh-bf,foo_scenario",
  135. },
  136. expectedScenarios: []string{"crowdsecurity/ssh-bf", "crowdsecurity/http-bf", "my_scenario", "foo_scenario"},
  137. },
  138. }
  139. for _, tc := range tests {
  140. tc := tc
  141. t.Run(tc.name, func(t *testing.T) {
  142. api := getAPIC(t)
  143. for machineID, scenarios := range tc.machineIDsWithScenarios {
  144. api.dbClient.Ent.Machine.Create().
  145. SetMachineId(machineID).
  146. SetPassword(testPassword.String()).
  147. SetIpAddress("1.2.3.4").
  148. SetScenarios(scenarios).
  149. ExecX(context.Background())
  150. }
  151. scenarios, err := api.FetchScenariosListFromDB()
  152. for machineID := range tc.machineIDsWithScenarios {
  153. api.dbClient.Ent.Machine.Delete().Where(machine.MachineIdEQ(machineID)).ExecX(context.Background())
  154. }
  155. require.NoError(t, err)
  156. assert.ElementsMatch(t, tc.expectedScenarios, scenarios)
  157. })
  158. }
  159. }
  160. func TestNewAPIC(t *testing.T) {
  161. var testConfig *csconfig.OnlineApiClientCfg
  162. setConfig := func() {
  163. testConfig = &csconfig.OnlineApiClientCfg{
  164. Credentials: &csconfig.ApiCredentialsCfg{
  165. URL: "http://foobar/",
  166. Login: "foo",
  167. Password: "bar",
  168. },
  169. }
  170. }
  171. type args struct {
  172. dbClient *database.Client
  173. consoleConfig *csconfig.ConsoleConfig
  174. }
  175. tests := []struct {
  176. name string
  177. args args
  178. expectedErr string
  179. action func()
  180. }{
  181. {
  182. name: "simple",
  183. action: func() {},
  184. args: args{
  185. dbClient: getDBClient(t),
  186. consoleConfig: LoadTestConfig(t).API.Server.ConsoleConfig,
  187. },
  188. },
  189. {
  190. name: "error in parsing URL",
  191. action: func() { testConfig.Credentials.URL = "foobar http://" },
  192. args: args{
  193. dbClient: getDBClient(t),
  194. consoleConfig: LoadTestConfig(t).API.Server.ConsoleConfig,
  195. },
  196. expectedErr: "first path segment in URL cannot contain colon",
  197. },
  198. }
  199. for _, tc := range tests {
  200. tc := tc
  201. t.Run(tc.name, func(t *testing.T) {
  202. setConfig()
  203. httpmock.Activate()
  204. defer httpmock.DeactivateAndReset()
  205. httpmock.RegisterResponder("POST", "http://foobar/v3/watchers/login", httpmock.NewBytesResponder(
  206. 200, jsonMarshalX(
  207. models.WatcherAuthResponse{
  208. Code: 200,
  209. Expire: "2023-01-12T22:51:43Z",
  210. Token: "MyToken",
  211. },
  212. ),
  213. ))
  214. tc.action()
  215. _, err := NewAPIC(testConfig, tc.args.dbClient, tc.args.consoleConfig, nil)
  216. cstest.RequireErrorContains(t, err, tc.expectedErr)
  217. })
  218. }
  219. }
  220. func TestAPICHandleDeletedDecisions(t *testing.T) {
  221. api := getAPIC(t)
  222. _, deleteCounters := makeAddAndDeleteCounters()
  223. decision1 := api.dbClient.Ent.Decision.Create().
  224. SetUntil(time.Now().Add(time.Hour)).
  225. SetScenario("crowdsec/test").
  226. SetType("ban").
  227. SetScope("IP").
  228. SetValue("1.2.3.4").
  229. SetOrigin(types.CAPIOrigin).
  230. SaveX(context.Background())
  231. api.dbClient.Ent.Decision.Create().
  232. SetUntil(time.Now().Add(time.Hour)).
  233. SetScenario("crowdsec/test").
  234. SetType("ban").
  235. SetScope("IP").
  236. SetValue("1.2.3.4").
  237. SetOrigin(types.CAPIOrigin).
  238. SaveX(context.Background())
  239. assertTotalDecisionCount(t, api.dbClient, 2)
  240. nbDeleted, err := api.HandleDeletedDecisions([]*models.Decision{{
  241. Value: ptr.Of("1.2.3.4"),
  242. Origin: ptr.Of(types.CAPIOrigin),
  243. Type: &decision1.Type,
  244. Scenario: ptr.Of("crowdsec/test"),
  245. Scope: ptr.Of("IP"),
  246. }}, deleteCounters)
  247. assert.NoError(t, err)
  248. assert.Equal(t, 2, nbDeleted)
  249. assert.Equal(t, 2, deleteCounters[types.CAPIOrigin]["all"])
  250. }
  251. func TestAPICGetMetrics(t *testing.T) {
  252. cleanUp := func(api *apic) {
  253. api.dbClient.Ent.Bouncer.Delete().ExecX(context.Background())
  254. api.dbClient.Ent.Machine.Delete().ExecX(context.Background())
  255. }
  256. tests := []struct {
  257. name string
  258. machineIDs []string
  259. bouncers []string
  260. expectedMetric *models.Metrics
  261. }{
  262. {
  263. name: "no bouncers nor machines should still have bouncers/machines keys in output",
  264. machineIDs: []string{},
  265. bouncers: []string{},
  266. expectedMetric: &models.Metrics{
  267. ApilVersion: ptr.Of(version.String()),
  268. Bouncers: []*models.MetricsBouncerInfo{},
  269. Machines: []*models.MetricsAgentInfo{},
  270. },
  271. },
  272. {
  273. name: "simple",
  274. machineIDs: []string{"a", "b", "c"},
  275. bouncers: []string{"1", "2", "3"},
  276. expectedMetric: &models.Metrics{
  277. ApilVersion: ptr.Of(version.String()),
  278. Bouncers: []*models.MetricsBouncerInfo{
  279. {
  280. CustomName: "1",
  281. LastPull: time.Time{}.String(),
  282. }, {
  283. CustomName: "2",
  284. LastPull: time.Time{}.String(),
  285. }, {
  286. CustomName: "3",
  287. LastPull: time.Time{}.String(),
  288. },
  289. },
  290. Machines: []*models.MetricsAgentInfo{
  291. {
  292. Name: "a",
  293. LastPush: time.Time{}.String(),
  294. LastUpdate: time.Time{}.String(),
  295. },
  296. {
  297. Name: "b",
  298. LastPush: time.Time{}.String(),
  299. LastUpdate: time.Time{}.String(),
  300. },
  301. {
  302. Name: "c",
  303. LastPush: time.Time{}.String(),
  304. LastUpdate: time.Time{}.String(),
  305. },
  306. },
  307. },
  308. },
  309. }
  310. for _, tc := range tests {
  311. tc := tc
  312. t.Run(tc.name, func(t *testing.T) {
  313. apiClient := getAPIC(t)
  314. cleanUp(apiClient)
  315. for i, machineID := range tc.machineIDs {
  316. apiClient.dbClient.Ent.Machine.Create().
  317. SetMachineId(machineID).
  318. SetPassword(testPassword.String()).
  319. SetIpAddress(fmt.Sprintf("1.2.3.%d", i)).
  320. SetScenarios("crowdsecurity/test").
  321. SetLastPush(time.Time{}).
  322. SetUpdatedAt(time.Time{}).
  323. ExecX(context.Background())
  324. }
  325. for i, bouncerName := range tc.bouncers {
  326. apiClient.dbClient.Ent.Bouncer.Create().
  327. SetIPAddress(fmt.Sprintf("1.2.3.%d", i)).
  328. SetName(bouncerName).
  329. SetAPIKey("foobar").
  330. SetRevoked(false).
  331. SetLastPull(time.Time{}).
  332. ExecX(context.Background())
  333. }
  334. foundMetrics, err := apiClient.GetMetrics()
  335. require.NoError(t, err)
  336. assert.Equal(t, tc.expectedMetric.Bouncers, foundMetrics.Bouncers)
  337. assert.Equal(t, tc.expectedMetric.Machines, foundMetrics.Machines)
  338. })
  339. }
  340. }
  341. func TestCreateAlertsForDecision(t *testing.T) {
  342. httpBfDecisionList := &models.Decision{
  343. Origin: ptr.Of(types.ListOrigin),
  344. Scenario: ptr.Of("crowdsecurity/http-bf"),
  345. }
  346. sshBfDecisionList := &models.Decision{
  347. Origin: ptr.Of(types.ListOrigin),
  348. Scenario: ptr.Of("crowdsecurity/ssh-bf"),
  349. }
  350. httpBfDecisionCommunity := &models.Decision{
  351. Origin: ptr.Of(types.CAPIOrigin),
  352. Scenario: ptr.Of("crowdsecurity/http-bf"),
  353. }
  354. sshBfDecisionCommunity := &models.Decision{
  355. Origin: ptr.Of(types.CAPIOrigin),
  356. Scenario: ptr.Of("crowdsecurity/ssh-bf"),
  357. }
  358. type args struct {
  359. decisions []*models.Decision
  360. }
  361. tests := []struct {
  362. name string
  363. args args
  364. want []*models.Alert
  365. }{
  366. {
  367. name: "2 decisions CAPI List Decisions should create 2 alerts",
  368. args: args{
  369. decisions: []*models.Decision{
  370. httpBfDecisionList,
  371. sshBfDecisionList,
  372. },
  373. },
  374. want: []*models.Alert{
  375. createAlertForDecision(httpBfDecisionList),
  376. createAlertForDecision(sshBfDecisionList),
  377. },
  378. },
  379. {
  380. name: "2 decisions CAPI List same scenario decisions should create 1 alert",
  381. args: args{
  382. decisions: []*models.Decision{
  383. httpBfDecisionList,
  384. httpBfDecisionList,
  385. },
  386. },
  387. want: []*models.Alert{
  388. createAlertForDecision(httpBfDecisionList),
  389. },
  390. },
  391. {
  392. name: "5 decisions from community list should create 1 alert",
  393. args: args{
  394. decisions: []*models.Decision{
  395. httpBfDecisionCommunity,
  396. httpBfDecisionCommunity,
  397. sshBfDecisionCommunity,
  398. sshBfDecisionCommunity,
  399. sshBfDecisionCommunity,
  400. },
  401. },
  402. want: []*models.Alert{
  403. createAlertForDecision(sshBfDecisionCommunity),
  404. },
  405. },
  406. }
  407. for _, tc := range tests {
  408. tc := tc
  409. t.Run(tc.name, func(t *testing.T) {
  410. if got := createAlertsForDecisions(tc.args.decisions); !reflect.DeepEqual(got, tc.want) {
  411. t.Errorf("createAlertsForDecisions() = %v, want %v", got, tc.want)
  412. }
  413. })
  414. }
  415. }
  416. func TestFillAlertsWithDecisions(t *testing.T) {
  417. httpBfDecisionCommunity := &models.Decision{
  418. Origin: ptr.Of(types.CAPIOrigin),
  419. Scenario: ptr.Of("crowdsecurity/http-bf"),
  420. Scope: ptr.Of("ip"),
  421. }
  422. sshBfDecisionCommunity := &models.Decision{
  423. Origin: ptr.Of(types.CAPIOrigin),
  424. Scenario: ptr.Of("crowdsecurity/ssh-bf"),
  425. Scope: ptr.Of("ip"),
  426. }
  427. httpBfDecisionList := &models.Decision{
  428. Origin: ptr.Of(types.ListOrigin),
  429. Scenario: ptr.Of("crowdsecurity/http-bf"),
  430. Scope: ptr.Of("ip"),
  431. }
  432. sshBfDecisionList := &models.Decision{
  433. Origin: ptr.Of(types.ListOrigin),
  434. Scenario: ptr.Of("crowdsecurity/ssh-bf"),
  435. Scope: ptr.Of("ip"),
  436. }
  437. type args struct {
  438. alerts []*models.Alert
  439. decisions []*models.Decision
  440. }
  441. tests := []struct {
  442. name string
  443. args args
  444. want []*models.Alert
  445. }{
  446. {
  447. name: "1 CAPI alert should pair up with n CAPI decisions",
  448. args: args{
  449. alerts: []*models.Alert{createAlertForDecision(httpBfDecisionCommunity)},
  450. decisions: []*models.Decision{httpBfDecisionCommunity, sshBfDecisionCommunity, sshBfDecisionCommunity, httpBfDecisionCommunity},
  451. },
  452. want: []*models.Alert{
  453. func() *models.Alert {
  454. a := createAlertForDecision(httpBfDecisionCommunity)
  455. a.Decisions = []*models.Decision{httpBfDecisionCommunity, sshBfDecisionCommunity, sshBfDecisionCommunity, httpBfDecisionCommunity}
  456. return a
  457. }(),
  458. },
  459. },
  460. {
  461. name: "List alert should pair up only with decisions having same scenario",
  462. args: args{
  463. alerts: []*models.Alert{createAlertForDecision(httpBfDecisionList), createAlertForDecision(sshBfDecisionList)},
  464. decisions: []*models.Decision{httpBfDecisionList, httpBfDecisionList, sshBfDecisionList, sshBfDecisionList},
  465. },
  466. want: []*models.Alert{
  467. func() *models.Alert {
  468. a := createAlertForDecision(httpBfDecisionList)
  469. a.Decisions = []*models.Decision{httpBfDecisionList, httpBfDecisionList}
  470. return a
  471. }(),
  472. func() *models.Alert {
  473. a := createAlertForDecision(sshBfDecisionList)
  474. a.Decisions = []*models.Decision{sshBfDecisionList, sshBfDecisionList}
  475. return a
  476. }(),
  477. },
  478. },
  479. }
  480. for _, tc := range tests {
  481. tc := tc
  482. t.Run(tc.name, func(t *testing.T) {
  483. addCounters, _ := makeAddAndDeleteCounters()
  484. if got := fillAlertsWithDecisions(tc.args.alerts, tc.args.decisions, addCounters); !reflect.DeepEqual(got, tc.want) {
  485. t.Errorf("fillAlertsWithDecisions() = %v, want %v", got, tc.want)
  486. }
  487. })
  488. }
  489. }
  490. func TestAPICWhitelists(t *testing.T) {
  491. api := getAPIC(t)
  492. //one whitelist on IP, one on CIDR
  493. api.whitelists = &csconfig.CapiWhitelist{}
  494. ipwl1 := "9.2.3.4"
  495. ip := net.ParseIP(ipwl1)
  496. api.whitelists.Ips = append(api.whitelists.Ips, ip)
  497. ipwl1 = "7.2.3.4"
  498. ip = net.ParseIP(ipwl1)
  499. api.whitelists.Ips = append(api.whitelists.Ips, ip)
  500. cidrwl1 := "13.2.3.0/24"
  501. _, tnet, err := net.ParseCIDR(cidrwl1)
  502. if err != nil {
  503. t.Fatalf("unable to parse cidr : %s", err)
  504. }
  505. api.whitelists.Cidrs = append(api.whitelists.Cidrs, tnet)
  506. cidrwl1 = "11.2.3.0/24"
  507. _, tnet, err = net.ParseCIDR(cidrwl1)
  508. if err != nil {
  509. t.Fatalf("unable to parse cidr : %s", err)
  510. }
  511. api.whitelists.Cidrs = append(api.whitelists.Cidrs, tnet)
  512. api.dbClient.Ent.Decision.Create().
  513. SetOrigin(types.CAPIOrigin).
  514. SetType("ban").
  515. SetValue("9.9.9.9").
  516. SetScope("Ip").
  517. SetScenario("crowdsecurity/ssh-bf").
  518. SetUntil(time.Now().Add(time.Hour)).
  519. ExecX(context.Background())
  520. assertTotalDecisionCount(t, api.dbClient, 1)
  521. assertTotalValidDecisionCount(t, api.dbClient, 1)
  522. httpmock.Activate()
  523. defer httpmock.DeactivateAndReset()
  524. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/api/decisions/stream", httpmock.NewBytesResponder(
  525. 200, jsonMarshalX(
  526. modelscapi.GetDecisionsStreamResponse{
  527. Deleted: modelscapi.GetDecisionsStreamResponseDeleted{
  528. &modelscapi.GetDecisionsStreamResponseDeletedItem{
  529. Decisions: []string{
  530. "9.9.9.9", // This is already present in DB
  531. "9.1.9.9", // This not present in DB
  532. },
  533. Scope: ptr.Of("Ip"),
  534. }, // This is already present in DB
  535. },
  536. New: modelscapi.GetDecisionsStreamResponseNew{
  537. &modelscapi.GetDecisionsStreamResponseNewItem{
  538. Scenario: ptr.Of("crowdsecurity/test1"),
  539. Scope: ptr.Of("Ip"),
  540. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  541. {
  542. Value: ptr.Of("13.2.3.4"), //wl by cidr
  543. Duration: ptr.Of("24h"),
  544. },
  545. },
  546. },
  547. &modelscapi.GetDecisionsStreamResponseNewItem{
  548. Scenario: ptr.Of("crowdsecurity/test1"),
  549. Scope: ptr.Of("Ip"),
  550. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  551. {
  552. Value: ptr.Of("2.2.3.4"),
  553. Duration: ptr.Of("24h"),
  554. },
  555. },
  556. },
  557. &modelscapi.GetDecisionsStreamResponseNewItem{
  558. Scenario: ptr.Of("crowdsecurity/test2"),
  559. Scope: ptr.Of("Ip"),
  560. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  561. {
  562. Value: ptr.Of("13.2.3.5"), //wl by cidr
  563. Duration: ptr.Of("24h"),
  564. },
  565. },
  566. }, // These two are from community list.
  567. &modelscapi.GetDecisionsStreamResponseNewItem{
  568. Scenario: ptr.Of("crowdsecurity/test1"),
  569. Scope: ptr.Of("Ip"),
  570. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  571. {
  572. Value: ptr.Of("6.2.3.4"),
  573. Duration: ptr.Of("24h"),
  574. },
  575. },
  576. },
  577. &modelscapi.GetDecisionsStreamResponseNewItem{
  578. Scenario: ptr.Of("crowdsecurity/test1"),
  579. Scope: ptr.Of("Ip"),
  580. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  581. {
  582. Value: ptr.Of("9.2.3.4"), //wl by ip
  583. Duration: ptr.Of("24h"),
  584. },
  585. },
  586. },
  587. },
  588. Links: &modelscapi.GetDecisionsStreamResponseLinks{
  589. Blocklists: []*modelscapi.BlocklistLink{
  590. {
  591. URL: ptr.Of("http://api.crowdsec.net/blocklist1"),
  592. Name: ptr.Of("blocklist1"),
  593. Scope: ptr.Of("Ip"),
  594. Remediation: ptr.Of("ban"),
  595. Duration: ptr.Of("24h"),
  596. },
  597. {
  598. URL: ptr.Of("http://api.crowdsec.net/blocklist2"),
  599. Name: ptr.Of("blocklist2"),
  600. Scope: ptr.Of("Ip"),
  601. Remediation: ptr.Of("ban"),
  602. Duration: ptr.Of("24h"),
  603. },
  604. },
  605. },
  606. },
  607. ),
  608. ))
  609. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", httpmock.NewStringResponder(
  610. 200, "1.2.3.6",
  611. ))
  612. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist2", httpmock.NewStringResponder(
  613. 200, "1.2.3.7",
  614. ))
  615. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  616. require.NoError(t, err)
  617. apic, err := apiclient.NewDefaultClient(
  618. url,
  619. "/api",
  620. fmt.Sprintf("crowdsec/%s", version.String()),
  621. nil,
  622. )
  623. require.NoError(t, err)
  624. api.apiClient = apic
  625. err = api.PullTop(false)
  626. require.NoError(t, err)
  627. assertTotalDecisionCount(t, api.dbClient, 5) //2 from FIRE + 2 from bl + 1 existing
  628. assertTotalValidDecisionCount(t, api.dbClient, 4)
  629. assertTotalAlertCount(t, api.dbClient, 3) // 2 for list sub , 1 for community list.
  630. alerts := api.dbClient.Ent.Alert.Query().AllX(context.Background())
  631. validDecisions := api.dbClient.Ent.Decision.Query().Where(
  632. decision.UntilGT(time.Now())).
  633. AllX(context.Background())
  634. decisionScenarioFreq := make(map[string]int)
  635. decisionIp := make(map[string]int)
  636. alertScenario := make(map[string]int)
  637. for _, alert := range alerts {
  638. alertScenario[alert.SourceScope]++
  639. }
  640. assert.Equal(t, 3, len(alertScenario))
  641. assert.Equal(t, 1, alertScenario[SCOPE_CAPI_ALIAS_ALIAS])
  642. assert.Equal(t, 1, alertScenario["lists:blocklist1"])
  643. assert.Equal(t, 1, alertScenario["lists:blocklist2"])
  644. for _, decisions := range validDecisions {
  645. decisionScenarioFreq[decisions.Scenario]++
  646. decisionIp[decisions.Value]++
  647. }
  648. assert.Equal(t, 1, decisionIp["2.2.3.4"], 1)
  649. assert.Equal(t, 1, decisionIp["6.2.3.4"], 1)
  650. if _, ok := decisionIp["13.2.3.4"]; ok {
  651. t.Errorf("13.2.3.4 is whitelisted")
  652. }
  653. if _, ok := decisionIp["13.2.3.5"]; ok {
  654. t.Errorf("13.2.3.5 is whitelisted")
  655. }
  656. if _, ok := decisionIp["9.2.3.4"]; ok {
  657. t.Errorf("9.2.3.4 is whitelisted")
  658. }
  659. assert.Equal(t, 1, decisionScenarioFreq["blocklist1"], 1)
  660. assert.Equal(t, 1, decisionScenarioFreq["blocklist2"], 1)
  661. assert.Equal(t, 2, decisionScenarioFreq["crowdsecurity/test1"], 2)
  662. }
  663. func TestAPICPullTop(t *testing.T) {
  664. api := getAPIC(t)
  665. api.dbClient.Ent.Decision.Create().
  666. SetOrigin(types.CAPIOrigin).
  667. SetType("ban").
  668. SetValue("9.9.9.9").
  669. SetScope("Ip").
  670. SetScenario("crowdsecurity/ssh-bf").
  671. SetUntil(time.Now().Add(time.Hour)).
  672. ExecX(context.Background())
  673. assertTotalDecisionCount(t, api.dbClient, 1)
  674. assertTotalValidDecisionCount(t, api.dbClient, 1)
  675. httpmock.Activate()
  676. defer httpmock.DeactivateAndReset()
  677. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/api/decisions/stream", httpmock.NewBytesResponder(
  678. 200, jsonMarshalX(
  679. modelscapi.GetDecisionsStreamResponse{
  680. Deleted: modelscapi.GetDecisionsStreamResponseDeleted{
  681. &modelscapi.GetDecisionsStreamResponseDeletedItem{
  682. Decisions: []string{
  683. "9.9.9.9", // This is already present in DB
  684. "9.1.9.9", // This not present in DB
  685. },
  686. Scope: ptr.Of("Ip"),
  687. }, // This is already present in DB
  688. },
  689. New: modelscapi.GetDecisionsStreamResponseNew{
  690. &modelscapi.GetDecisionsStreamResponseNewItem{
  691. Scenario: ptr.Of("crowdsecurity/test1"),
  692. Scope: ptr.Of("Ip"),
  693. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  694. {
  695. Value: ptr.Of("1.2.3.4"),
  696. Duration: ptr.Of("24h"),
  697. },
  698. },
  699. },
  700. &modelscapi.GetDecisionsStreamResponseNewItem{
  701. Scenario: ptr.Of("crowdsecurity/test2"),
  702. Scope: ptr.Of("Ip"),
  703. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  704. {
  705. Value: ptr.Of("1.2.3.5"),
  706. Duration: ptr.Of("24h"),
  707. },
  708. },
  709. }, // These two are from community list.
  710. },
  711. Links: &modelscapi.GetDecisionsStreamResponseLinks{
  712. Blocklists: []*modelscapi.BlocklistLink{
  713. {
  714. URL: ptr.Of("http://api.crowdsec.net/blocklist1"),
  715. Name: ptr.Of("blocklist1"),
  716. Scope: ptr.Of("Ip"),
  717. Remediation: ptr.Of("ban"),
  718. Duration: ptr.Of("24h"),
  719. },
  720. {
  721. URL: ptr.Of("http://api.crowdsec.net/blocklist2"),
  722. Name: ptr.Of("blocklist2"),
  723. Scope: ptr.Of("Ip"),
  724. Remediation: ptr.Of("ban"),
  725. Duration: ptr.Of("24h"),
  726. },
  727. },
  728. },
  729. },
  730. ),
  731. ))
  732. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", httpmock.NewStringResponder(
  733. 200, "1.2.3.6",
  734. ))
  735. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist2", httpmock.NewStringResponder(
  736. 200, "1.2.3.7",
  737. ))
  738. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  739. require.NoError(t, err)
  740. apic, err := apiclient.NewDefaultClient(
  741. url,
  742. "/api",
  743. fmt.Sprintf("crowdsec/%s", version.String()),
  744. nil,
  745. )
  746. require.NoError(t, err)
  747. api.apiClient = apic
  748. err = api.PullTop(false)
  749. require.NoError(t, err)
  750. assertTotalDecisionCount(t, api.dbClient, 5)
  751. assertTotalValidDecisionCount(t, api.dbClient, 4)
  752. assertTotalAlertCount(t, api.dbClient, 3) // 2 for list sub , 1 for community list.
  753. alerts := api.dbClient.Ent.Alert.Query().AllX(context.Background())
  754. validDecisions := api.dbClient.Ent.Decision.Query().Where(
  755. decision.UntilGT(time.Now())).
  756. AllX(context.Background())
  757. decisionScenarioFreq := make(map[string]int)
  758. alertScenario := make(map[string]int)
  759. for _, alert := range alerts {
  760. alertScenario[alert.SourceScope]++
  761. }
  762. assert.Equal(t, 3, len(alertScenario))
  763. assert.Equal(t, 1, alertScenario[SCOPE_CAPI_ALIAS_ALIAS])
  764. assert.Equal(t, 1, alertScenario["lists:blocklist1"])
  765. assert.Equal(t, 1, alertScenario["lists:blocklist2"])
  766. for _, decisions := range validDecisions {
  767. decisionScenarioFreq[decisions.Scenario]++
  768. }
  769. assert.Equal(t, 1, decisionScenarioFreq["blocklist1"], 1)
  770. assert.Equal(t, 1, decisionScenarioFreq["blocklist2"], 1)
  771. assert.Equal(t, 1, decisionScenarioFreq["crowdsecurity/test1"], 1)
  772. assert.Equal(t, 1, decisionScenarioFreq["crowdsecurity/test2"], 1)
  773. }
  774. func TestAPICPullTopBLCacheFirstCall(t *testing.T) {
  775. // no decision in db, no last modified parameter.
  776. api := getAPIC(t)
  777. httpmock.Activate()
  778. defer httpmock.DeactivateAndReset()
  779. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/api/decisions/stream", httpmock.NewBytesResponder(
  780. 200, jsonMarshalX(
  781. modelscapi.GetDecisionsStreamResponse{
  782. New: modelscapi.GetDecisionsStreamResponseNew{
  783. &modelscapi.GetDecisionsStreamResponseNewItem{
  784. Scenario: ptr.Of("crowdsecurity/test1"),
  785. Scope: ptr.Of("Ip"),
  786. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  787. {
  788. Value: ptr.Of("1.2.3.4"),
  789. Duration: ptr.Of("24h"),
  790. },
  791. },
  792. },
  793. },
  794. Links: &modelscapi.GetDecisionsStreamResponseLinks{
  795. Blocklists: []*modelscapi.BlocklistLink{
  796. {
  797. URL: ptr.Of("http://api.crowdsec.net/blocklist1"),
  798. Name: ptr.Of("blocklist1"),
  799. Scope: ptr.Of("Ip"),
  800. Remediation: ptr.Of("ban"),
  801. Duration: ptr.Of("24h"),
  802. },
  803. },
  804. },
  805. },
  806. ),
  807. ))
  808. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", func(req *http.Request) (*http.Response, error) {
  809. assert.Equal(t, "", req.Header.Get("If-Modified-Since"))
  810. return httpmock.NewStringResponse(200, "1.2.3.4"), nil
  811. })
  812. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  813. require.NoError(t, err)
  814. apic, err := apiclient.NewDefaultClient(
  815. url,
  816. "/api",
  817. fmt.Sprintf("crowdsec/%s", version.String()),
  818. nil,
  819. )
  820. require.NoError(t, err)
  821. api.apiClient = apic
  822. err = api.PullTop(false)
  823. require.NoError(t, err)
  824. blocklistConfigItemName := "blocklist:blocklist1:last_pull"
  825. lastPullTimestamp, err := api.dbClient.GetConfigItem(blocklistConfigItemName)
  826. require.NoError(t, err)
  827. assert.NotEqual(t, "", *lastPullTimestamp)
  828. // new call should return 304 and should not change lastPullTimestamp
  829. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", func(req *http.Request) (*http.Response, error) {
  830. assert.NotEqual(t, "", req.Header.Get("If-Modified-Since"))
  831. return httpmock.NewStringResponse(304, ""), nil
  832. })
  833. err = api.PullTop(false)
  834. require.NoError(t, err)
  835. secondLastPullTimestamp, err := api.dbClient.GetConfigItem(blocklistConfigItemName)
  836. require.NoError(t, err)
  837. assert.Equal(t, *lastPullTimestamp, *secondLastPullTimestamp)
  838. }
  839. func TestAPICPullTopBLCacheForceCall(t *testing.T) {
  840. api := getAPIC(t)
  841. httpmock.Activate()
  842. defer httpmock.DeactivateAndReset()
  843. // create a decision about to expire. It should force fetch
  844. alertInstance := api.dbClient.Ent.Alert.
  845. Create().
  846. SetScenario("update list").
  847. SetSourceScope("list:blocklist1").
  848. SetSourceValue("list:blocklist1").
  849. SaveX(context.Background())
  850. api.dbClient.Ent.Decision.Create().
  851. SetOrigin(types.ListOrigin).
  852. SetType("ban").
  853. SetValue("9.9.9.9").
  854. SetScope("Ip").
  855. SetScenario("blocklist1").
  856. SetUntil(time.Now().Add(time.Hour)).
  857. SetOwnerID(alertInstance.ID).
  858. ExecX(context.Background())
  859. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/api/decisions/stream", httpmock.NewBytesResponder(
  860. 200, jsonMarshalX(
  861. modelscapi.GetDecisionsStreamResponse{
  862. New: modelscapi.GetDecisionsStreamResponseNew{
  863. &modelscapi.GetDecisionsStreamResponseNewItem{
  864. Scenario: ptr.Of("crowdsecurity/test1"),
  865. Scope: ptr.Of("Ip"),
  866. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  867. {
  868. Value: ptr.Of("1.2.3.4"),
  869. Duration: ptr.Of("24h"),
  870. },
  871. },
  872. },
  873. },
  874. Links: &modelscapi.GetDecisionsStreamResponseLinks{
  875. Blocklists: []*modelscapi.BlocklistLink{
  876. {
  877. URL: ptr.Of("http://api.crowdsec.net/blocklist1"),
  878. Name: ptr.Of("blocklist1"),
  879. Scope: ptr.Of("Ip"),
  880. Remediation: ptr.Of("ban"),
  881. Duration: ptr.Of("24h"),
  882. },
  883. },
  884. },
  885. },
  886. ),
  887. ))
  888. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", func(req *http.Request) (*http.Response, error) {
  889. assert.Equal(t, "", req.Header.Get("If-Modified-Since"))
  890. return httpmock.NewStringResponse(304, ""), nil
  891. })
  892. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  893. require.NoError(t, err)
  894. apic, err := apiclient.NewDefaultClient(
  895. url,
  896. "/api",
  897. fmt.Sprintf("crowdsec/%s", version.String()),
  898. nil,
  899. )
  900. require.NoError(t, err)
  901. api.apiClient = apic
  902. err = api.PullTop(false)
  903. require.NoError(t, err)
  904. }
  905. func TestAPICPush(t *testing.T) {
  906. tests := []struct {
  907. name string
  908. alerts []*models.Alert
  909. expectedCalls int
  910. }{
  911. {
  912. name: "simple single alert",
  913. alerts: []*models.Alert{
  914. {
  915. Scenario: ptr.Of("crowdsec/test"),
  916. ScenarioHash: ptr.Of("certified"),
  917. ScenarioVersion: ptr.Of("v1.0"),
  918. Simulated: ptr.Of(false),
  919. Source: &models.Source{},
  920. },
  921. },
  922. expectedCalls: 1,
  923. },
  924. {
  925. name: "simulated alert is not pushed",
  926. alerts: []*models.Alert{
  927. {
  928. Scenario: ptr.Of("crowdsec/test"),
  929. ScenarioHash: ptr.Of("certified"),
  930. ScenarioVersion: ptr.Of("v1.0"),
  931. Simulated: ptr.Of(true),
  932. Source: &models.Source{},
  933. },
  934. },
  935. expectedCalls: 0,
  936. },
  937. {
  938. name: "1 request per 50 alerts",
  939. expectedCalls: 2,
  940. alerts: func() []*models.Alert {
  941. alerts := make([]*models.Alert, 100)
  942. for i := 0; i < 100; i++ {
  943. alerts[i] = &models.Alert{
  944. Scenario: ptr.Of("crowdsec/test"),
  945. ScenarioHash: ptr.Of("certified"),
  946. ScenarioVersion: ptr.Of("v1.0"),
  947. Simulated: ptr.Of(false),
  948. Source: &models.Source{},
  949. }
  950. }
  951. return alerts
  952. }(),
  953. },
  954. }
  955. for _, tc := range tests {
  956. tc := tc
  957. t.Run(tc.name, func(t *testing.T) {
  958. api := getAPIC(t)
  959. api.pushInterval = time.Millisecond
  960. api.pushIntervalFirst = time.Millisecond
  961. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  962. require.NoError(t, err)
  963. httpmock.Activate()
  964. defer httpmock.DeactivateAndReset()
  965. apic, err := apiclient.NewDefaultClient(
  966. url,
  967. "/api",
  968. fmt.Sprintf("crowdsec/%s", version.String()),
  969. nil,
  970. )
  971. require.NoError(t, err)
  972. api.apiClient = apic
  973. httpmock.RegisterResponder("POST", "http://api.crowdsec.net/api/signals", httpmock.NewBytesResponder(200, []byte{}))
  974. go func() {
  975. api.AlertsAddChan <- tc.alerts
  976. time.Sleep(time.Second)
  977. api.Shutdown()
  978. }()
  979. err = api.Push()
  980. require.NoError(t, err)
  981. assert.Equal(t, tc.expectedCalls, httpmock.GetTotalCallCount())
  982. })
  983. }
  984. }
  985. func TestAPICPull(t *testing.T) {
  986. api := getAPIC(t)
  987. tests := []struct {
  988. name string
  989. setUp func()
  990. expectedDecisionCount int
  991. logContains string
  992. }{
  993. {
  994. name: "test pull if no scenarios are present",
  995. setUp: func() {},
  996. logContains: "scenario list is empty, will not pull yet",
  997. },
  998. {
  999. name: "test pull",
  1000. setUp: func() {
  1001. api.dbClient.Ent.Machine.Create().
  1002. SetMachineId("1.2.3.4").
  1003. SetPassword(testPassword.String()).
  1004. SetIpAddress("1.2.3.4").
  1005. SetScenarios("crowdsecurity/ssh-bf").
  1006. ExecX(context.Background())
  1007. },
  1008. expectedDecisionCount: 1,
  1009. },
  1010. }
  1011. for _, tc := range tests {
  1012. tc := tc
  1013. t.Run(tc.name, func(t *testing.T) {
  1014. api = getAPIC(t)
  1015. api.pullInterval = time.Millisecond
  1016. api.pullIntervalFirst = time.Millisecond
  1017. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  1018. require.NoError(t, err)
  1019. httpmock.Activate()
  1020. defer httpmock.DeactivateAndReset()
  1021. apic, err := apiclient.NewDefaultClient(
  1022. url,
  1023. "/api",
  1024. fmt.Sprintf("crowdsec/%s", version.String()),
  1025. nil,
  1026. )
  1027. require.NoError(t, err)
  1028. api.apiClient = apic
  1029. httpmock.RegisterNoResponder(httpmock.NewBytesResponder(200, jsonMarshalX(
  1030. modelscapi.GetDecisionsStreamResponse{
  1031. New: modelscapi.GetDecisionsStreamResponseNew{
  1032. &modelscapi.GetDecisionsStreamResponseNewItem{
  1033. Scenario: ptr.Of("crowdsecurity/ssh-bf"),
  1034. Scope: ptr.Of("Ip"),
  1035. Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
  1036. {
  1037. Value: ptr.Of("1.2.3.5"),
  1038. Duration: ptr.Of("24h"),
  1039. },
  1040. },
  1041. },
  1042. },
  1043. },
  1044. )))
  1045. tc.setUp()
  1046. var buf bytes.Buffer
  1047. go func() {
  1048. logrus.SetOutput(&buf)
  1049. if err := api.Pull(); err != nil {
  1050. panic(err)
  1051. }
  1052. }()
  1053. //Slightly long because the CI runner for windows are slow, and this can lead to random failure
  1054. time.Sleep(time.Millisecond * 500)
  1055. logrus.SetOutput(os.Stderr)
  1056. assert.Contains(t, buf.String(), tc.logContains)
  1057. assertTotalDecisionCount(t, api.dbClient, tc.expectedDecisionCount)
  1058. })
  1059. }
  1060. }
  1061. func TestShouldShareAlert(t *testing.T) {
  1062. tests := []struct {
  1063. name string
  1064. consoleConfig *csconfig.ConsoleConfig
  1065. alert *models.Alert
  1066. expectedRet bool
  1067. expectedTrust string
  1068. }{
  1069. {
  1070. name: "custom alert should be shared if config enables it",
  1071. consoleConfig: &csconfig.ConsoleConfig{
  1072. ShareCustomScenarios: ptr.Of(true),
  1073. },
  1074. alert: &models.Alert{Simulated: ptr.Of(false)},
  1075. expectedRet: true,
  1076. expectedTrust: "custom",
  1077. },
  1078. {
  1079. name: "custom alert should not be shared if config disables it",
  1080. consoleConfig: &csconfig.ConsoleConfig{
  1081. ShareCustomScenarios: ptr.Of(false),
  1082. },
  1083. alert: &models.Alert{Simulated: ptr.Of(false)},
  1084. expectedRet: false,
  1085. expectedTrust: "custom",
  1086. },
  1087. {
  1088. name: "manual alert should be shared if config enables it",
  1089. consoleConfig: &csconfig.ConsoleConfig{
  1090. ShareManualDecisions: ptr.Of(true),
  1091. },
  1092. alert: &models.Alert{
  1093. Simulated: ptr.Of(false),
  1094. Decisions: []*models.Decision{{Origin: ptr.Of(types.CscliOrigin)}},
  1095. },
  1096. expectedRet: true,
  1097. expectedTrust: "manual",
  1098. },
  1099. {
  1100. name: "manual alert should not be shared if config disables it",
  1101. consoleConfig: &csconfig.ConsoleConfig{
  1102. ShareManualDecisions: ptr.Of(false),
  1103. },
  1104. alert: &models.Alert{
  1105. Simulated: ptr.Of(false),
  1106. Decisions: []*models.Decision{{Origin: ptr.Of(types.CscliOrigin)}},
  1107. },
  1108. expectedRet: false,
  1109. expectedTrust: "manual",
  1110. },
  1111. {
  1112. name: "manual alert should be shared if config enables it",
  1113. consoleConfig: &csconfig.ConsoleConfig{
  1114. ShareTaintedScenarios: ptr.Of(true),
  1115. },
  1116. alert: &models.Alert{
  1117. Simulated: ptr.Of(false),
  1118. ScenarioHash: ptr.Of("whateverHash"),
  1119. },
  1120. expectedRet: true,
  1121. expectedTrust: "tainted",
  1122. },
  1123. {
  1124. name: "manual alert should not be shared if config disables it",
  1125. consoleConfig: &csconfig.ConsoleConfig{
  1126. ShareTaintedScenarios: ptr.Of(false),
  1127. },
  1128. alert: &models.Alert{
  1129. Simulated: ptr.Of(false),
  1130. ScenarioHash: ptr.Of("whateverHash"),
  1131. },
  1132. expectedRet: false,
  1133. expectedTrust: "tainted",
  1134. },
  1135. }
  1136. for _, tc := range tests {
  1137. tc := tc
  1138. t.Run(tc.name, func(t *testing.T) {
  1139. ret := shouldShareAlert(tc.alert, tc.consoleConfig)
  1140. assert.Equal(t, tc.expectedRet, ret)
  1141. })
  1142. }
  1143. }