apic_test.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. package apiserver
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "net/url"
  8. "os"
  9. "reflect"
  10. "sort"
  11. "sync"
  12. "testing"
  13. "time"
  14. "github.com/crowdsecurity/crowdsec/pkg/apiclient"
  15. "github.com/crowdsecurity/crowdsec/pkg/csconfig"
  16. "github.com/crowdsecurity/crowdsec/pkg/cwversion"
  17. "github.com/crowdsecurity/crowdsec/pkg/database"
  18. "github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
  19. "github.com/crowdsecurity/crowdsec/pkg/database/ent/machine"
  20. "github.com/crowdsecurity/crowdsec/pkg/models"
  21. "github.com/crowdsecurity/crowdsec/pkg/types"
  22. "github.com/jarcoal/httpmock"
  23. "github.com/sirupsen/logrus"
  24. "github.com/stretchr/testify/assert"
  25. "gopkg.in/tomb.v2"
  26. )
  27. func getDBClient(t *testing.T) *database.Client {
  28. t.Helper()
  29. dbPath, err := os.CreateTemp("", "*sqlite")
  30. if err != nil {
  31. t.Fatal(err)
  32. }
  33. dbClient, err := database.NewClient(&csconfig.DatabaseCfg{
  34. Type: "sqlite",
  35. DbName: "crowdsec",
  36. DbPath: dbPath.Name(),
  37. })
  38. if err != nil {
  39. t.Fatal(err)
  40. }
  41. return dbClient
  42. }
  43. func getAPIC(t *testing.T) *apic {
  44. t.Helper()
  45. dbClient := getDBClient(t)
  46. return &apic{
  47. alertToPush: make(chan []*models.Alert),
  48. dbClient: dbClient,
  49. mu: sync.Mutex{},
  50. startup: true,
  51. pullTomb: tomb.Tomb{},
  52. pushTomb: tomb.Tomb{},
  53. metricsTomb: tomb.Tomb{},
  54. scenarioList: make([]string, 0),
  55. consoleConfig: &csconfig.ConsoleConfig{
  56. ShareManualDecisions: types.BoolPtr(false),
  57. ShareTaintedScenarios: types.BoolPtr(false),
  58. ShareCustomScenarios: types.BoolPtr(false),
  59. },
  60. }
  61. }
  62. func absDiff(a int, b int) (c int) {
  63. if c = a - b; c < 0 {
  64. return -1 * c
  65. }
  66. return c
  67. }
  68. func assertTotalDecisionCount(t *testing.T, dbClient *database.Client, count int) {
  69. d := dbClient.Ent.Decision.Query().AllX(context.Background())
  70. assert.Len(t, d, count)
  71. }
  72. func assertTotalValidDecisionCount(t *testing.T, dbClient *database.Client, count int) {
  73. d := dbClient.Ent.Decision.Query().Where(
  74. decision.UntilGT(time.Now()),
  75. ).AllX(context.Background())
  76. assert.Len(t, d, count)
  77. }
  78. func jsonMarshalX(v interface{}) []byte {
  79. data, err := json.Marshal(v)
  80. if err != nil {
  81. panic(err)
  82. }
  83. return data
  84. }
  85. func assertTotalAlertCount(t *testing.T, dbClient *database.Client, count int) {
  86. d := dbClient.Ent.Alert.Query().AllX(context.Background())
  87. assert.Len(t, d, count)
  88. }
  89. func TestAPICCAPIPullIsOld(t *testing.T) {
  90. api := getAPIC(t)
  91. isOld, err := api.CAPIPullIsOld()
  92. if err != nil {
  93. t.Fatal(err)
  94. }
  95. assert.True(t, isOld)
  96. decision := api.dbClient.Ent.Decision.Create().
  97. SetUntil(time.Now().Add(time.Hour)).
  98. SetScenario("crowdsec/test").
  99. SetType("IP").
  100. SetScope("Country").
  101. SetValue("Blah").
  102. SetOrigin(SCOPE_CAPI).
  103. SaveX(context.Background())
  104. api.dbClient.Ent.Alert.Create().
  105. SetCreatedAt(time.Now()).
  106. SetScenario("crowdsec/test").
  107. AddDecisions(
  108. decision,
  109. ).
  110. SaveX(context.Background())
  111. isOld, err = api.CAPIPullIsOld()
  112. if err != nil {
  113. t.Fatal(err)
  114. }
  115. assert.False(t, isOld)
  116. }
  117. func TestAPICFetchScenariosListFromDB(t *testing.T) {
  118. api := getAPIC(t)
  119. testCases := []struct {
  120. name string
  121. machineIDsWithScenarios map[string]string
  122. expectedScenarios []string
  123. }{
  124. {
  125. name: "Simple one machine with two scenarios",
  126. machineIDsWithScenarios: map[string]string{
  127. "a": "crowdsecurity/http-bf,crowdsecurity/ssh-bf",
  128. },
  129. expectedScenarios: []string{"crowdsecurity/ssh-bf", "crowdsecurity/http-bf"},
  130. },
  131. {
  132. name: "Multi machine with custom+hub scenarios",
  133. machineIDsWithScenarios: map[string]string{
  134. "a": "crowdsecurity/http-bf,crowdsecurity/ssh-bf,my_scenario",
  135. "b": "crowdsecurity/http-bf,crowdsecurity/ssh-bf,foo_scenario",
  136. },
  137. expectedScenarios: []string{"crowdsecurity/ssh-bf", "crowdsecurity/http-bf", "my_scenario", "foo_scenario"},
  138. },
  139. }
  140. for _, tc := range testCases {
  141. t.Run(tc.name, func(t *testing.T) {
  142. for machineID, scenarios := range tc.machineIDsWithScenarios {
  143. api.dbClient.Ent.Machine.Create().
  144. SetMachineId(machineID).
  145. SetPassword(testPassword.String()).
  146. SetIpAddress("1.2.3.4").
  147. SetScenarios(scenarios).
  148. ExecX(context.Background())
  149. }
  150. scenarios, err := api.FetchScenariosListFromDB()
  151. for machineID := range tc.machineIDsWithScenarios {
  152. api.dbClient.Ent.Machine.Delete().Where(machine.MachineIdEQ(machineID)).ExecX(context.Background())
  153. }
  154. if err != nil {
  155. t.Fatal(err)
  156. } else {
  157. sort.Strings(scenarios)
  158. sort.Strings(tc.expectedScenarios)
  159. assert.Equal(t, scenarios, tc.expectedScenarios)
  160. }
  161. })
  162. }
  163. }
  164. func TestNewAPIC(t *testing.T) {
  165. var testConfig *csconfig.OnlineApiClientCfg
  166. setConfig := func() {
  167. testConfig = &csconfig.OnlineApiClientCfg{
  168. Credentials: &csconfig.ApiCredentialsCfg{
  169. URL: "foobar",
  170. Login: "foo",
  171. Password: "bar",
  172. },
  173. }
  174. }
  175. type args struct {
  176. dbClient *database.Client
  177. consoleConfig *csconfig.ConsoleConfig
  178. }
  179. tests := []struct {
  180. name string
  181. args args
  182. wantErr bool
  183. errorContains string
  184. action func()
  185. }{
  186. {
  187. name: "simple",
  188. action: func() {},
  189. args: args{
  190. dbClient: getDBClient(t),
  191. consoleConfig: LoadTestConfig().API.Server.ConsoleConfig,
  192. },
  193. },
  194. {
  195. name: "error in parsing URL",
  196. action: func() { testConfig.Credentials.URL = "foobar http://" },
  197. args: args{
  198. dbClient: getDBClient(t),
  199. consoleConfig: LoadTestConfig().API.Server.ConsoleConfig,
  200. },
  201. wantErr: true,
  202. errorContains: "first path segment in URL cannot contain colon",
  203. },
  204. }
  205. for _, tt := range tests {
  206. t.Run(tt.name, func(t *testing.T) {
  207. setConfig()
  208. tt.action()
  209. _, err := NewAPIC(testConfig, tt.args.dbClient, tt.args.consoleConfig)
  210. if tt.wantErr {
  211. assert.ErrorContains(t, err, tt.errorContains)
  212. } else {
  213. assert.NoError(t, err)
  214. }
  215. })
  216. }
  217. }
  218. func TestAPICHandleDeletedDecisions(t *testing.T) {
  219. api := getAPIC(t)
  220. _, deleteCounters := makeAddAndDeleteCounters()
  221. decision1 := api.dbClient.Ent.Decision.Create().
  222. SetUntil(time.Now().Add(time.Hour)).
  223. SetScenario("crowdsec/test").
  224. SetType("ban").
  225. SetScope("IP").
  226. SetValue("1.2.3.4").
  227. SetOrigin(SCOPE_CAPI).
  228. SaveX(context.Background())
  229. api.dbClient.Ent.Decision.Create().
  230. SetUntil(time.Now().Add(time.Hour)).
  231. SetScenario("crowdsec/test").
  232. SetType("ban").
  233. SetScope("IP").
  234. SetValue("1.2.3.4").
  235. SetOrigin(SCOPE_CAPI).
  236. SaveX(context.Background())
  237. assertTotalDecisionCount(t, api.dbClient, 2)
  238. nbDeleted, err := api.HandleDeletedDecisions([]*models.Decision{{
  239. Value: types.StrPtr("1.2.3.4"),
  240. Origin: &SCOPE_CAPI,
  241. Type: &decision1.Type,
  242. Scenario: types.StrPtr("crowdsec/test"),
  243. Scope: types.StrPtr("IP"),
  244. }}, deleteCounters)
  245. assert.NoError(t, err)
  246. assert.Equal(t, nbDeleted, 2)
  247. assert.Equal(t, deleteCounters[SCOPE_CAPI]["all"], 2)
  248. }
  249. func TestAPICGetMetrics(t *testing.T) {
  250. api := getAPIC(t)
  251. cleanUp := func() {
  252. api.dbClient.Ent.Bouncer.Delete().ExecX(context.Background())
  253. api.dbClient.Ent.Machine.Delete().ExecX(context.Background())
  254. }
  255. testCases := []struct {
  256. name string
  257. machineIDs []string
  258. bouncers []string
  259. expectedMetric *models.Metrics
  260. }{
  261. {
  262. name: "simple",
  263. machineIDs: []string{"a", "b", "c"},
  264. bouncers: []string{"1", "2", "3"},
  265. expectedMetric: &models.Metrics{
  266. ApilVersion: types.StrPtr(cwversion.VersionStr()),
  267. Bouncers: []*models.MetricsBouncerInfo{
  268. {
  269. CustomName: "1",
  270. LastPull: time.Time{}.String(),
  271. }, {
  272. CustomName: "2",
  273. LastPull: time.Time{}.String(),
  274. }, {
  275. CustomName: "3",
  276. LastPull: time.Time{}.String(),
  277. },
  278. },
  279. Machines: []*models.MetricsAgentInfo{
  280. {
  281. Name: "a",
  282. LastPush: time.Time{}.String(),
  283. LastUpdate: time.Time{}.String(),
  284. },
  285. {
  286. Name: "b",
  287. LastPush: time.Time{}.String(),
  288. LastUpdate: time.Time{}.String(),
  289. },
  290. {
  291. Name: "c",
  292. LastPush: time.Time{}.String(),
  293. LastUpdate: time.Time{}.String(),
  294. },
  295. },
  296. },
  297. },
  298. }
  299. for _, testCase := range testCases {
  300. t.Run(testCase.name, func(t *testing.T) {
  301. cleanUp()
  302. for i, machineID := range testCase.machineIDs {
  303. api.dbClient.Ent.Machine.Create().
  304. SetMachineId(machineID).
  305. SetPassword(testPassword.String()).
  306. SetIpAddress(fmt.Sprintf("1.2.3.%d", i)).
  307. SetScenarios("crowdsecurity/test").
  308. SetLastPush(time.Time{}).
  309. SetUpdatedAt(time.Time{}).
  310. ExecX(context.Background())
  311. }
  312. for i, bouncerName := range testCase.bouncers {
  313. api.dbClient.Ent.Bouncer.Create().
  314. SetIPAddress(fmt.Sprintf("1.2.3.%d", i)).
  315. SetName(bouncerName).
  316. SetAPIKey("foobar").
  317. SetRevoked(false).
  318. SetLastPull(time.Time{}).
  319. ExecX(context.Background())
  320. }
  321. if foundMetrics, err := api.GetMetrics(); err != nil {
  322. t.Fatal(err)
  323. } else {
  324. assert.Equal(t, foundMetrics.Bouncers, testCase.expectedMetric.Bouncers)
  325. assert.Equal(t, foundMetrics.Machines, testCase.expectedMetric.Machines)
  326. }
  327. })
  328. }
  329. }
  330. func TestCreateAlertsForDecision(t *testing.T) {
  331. httpBfDecisionList := &models.Decision{
  332. Origin: &SCOPE_LISTS,
  333. Scenario: types.StrPtr("crowdsecurity/http-bf"),
  334. }
  335. sshBfDecisionList := &models.Decision{
  336. Origin: &SCOPE_LISTS,
  337. Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
  338. }
  339. httpBfDecisionCommunity := &models.Decision{
  340. Origin: &SCOPE_CAPI,
  341. Scenario: types.StrPtr("crowdsecurity/http-bf"),
  342. }
  343. sshBfDecisionCommunity := &models.Decision{
  344. Origin: &SCOPE_CAPI,
  345. Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
  346. }
  347. type args struct {
  348. decisions []*models.Decision
  349. }
  350. tests := []struct {
  351. name string
  352. args args
  353. want []*models.Alert
  354. }{
  355. {
  356. name: "2 decisions CAPI List Decisions should create 2 alerts",
  357. args: args{
  358. decisions: []*models.Decision{
  359. httpBfDecisionList,
  360. sshBfDecisionList,
  361. },
  362. },
  363. want: []*models.Alert{
  364. createAlertForDecision(httpBfDecisionList),
  365. createAlertForDecision(sshBfDecisionList),
  366. },
  367. },
  368. {
  369. name: "2 decisions CAPI List same scenario decisions should create 1 alert",
  370. args: args{
  371. decisions: []*models.Decision{
  372. httpBfDecisionList,
  373. httpBfDecisionList,
  374. },
  375. },
  376. want: []*models.Alert{
  377. createAlertForDecision(httpBfDecisionList),
  378. },
  379. },
  380. {
  381. name: "5 decisions from community list should create 1 alert",
  382. args: args{
  383. decisions: []*models.Decision{
  384. httpBfDecisionCommunity,
  385. httpBfDecisionCommunity,
  386. sshBfDecisionCommunity,
  387. sshBfDecisionCommunity,
  388. sshBfDecisionCommunity,
  389. },
  390. },
  391. want: []*models.Alert{
  392. createAlertForDecision(sshBfDecisionCommunity),
  393. },
  394. },
  395. }
  396. for _, tt := range tests {
  397. t.Run(tt.name, func(t *testing.T) {
  398. if got := createAlertsForDecisions(tt.args.decisions); !reflect.DeepEqual(got, tt.want) {
  399. t.Errorf("createAlertsForDecisions() = %v, want %v", got, tt.want)
  400. }
  401. })
  402. }
  403. }
  404. func TestFillAlertsWithDecisions(t *testing.T) {
  405. httpBfDecisionCommunity := &models.Decision{
  406. Origin: &SCOPE_CAPI,
  407. Scenario: types.StrPtr("crowdsecurity/http-bf"),
  408. Scope: types.StrPtr("ip"),
  409. }
  410. sshBfDecisionCommunity := &models.Decision{
  411. Origin: &SCOPE_CAPI,
  412. Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
  413. Scope: types.StrPtr("ip"),
  414. }
  415. httpBfDecisionList := &models.Decision{
  416. Origin: &SCOPE_LISTS,
  417. Scenario: types.StrPtr("crowdsecurity/http-bf"),
  418. Scope: types.StrPtr("ip"),
  419. }
  420. sshBfDecisionList := &models.Decision{
  421. Origin: &SCOPE_LISTS,
  422. Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
  423. Scope: types.StrPtr("ip"),
  424. }
  425. type args struct {
  426. alerts []*models.Alert
  427. decisions []*models.Decision
  428. }
  429. tests := []struct {
  430. name string
  431. args args
  432. want []*models.Alert
  433. }{
  434. {
  435. name: "1 CAPI alert should pair up with n CAPI decisions",
  436. args: args{
  437. alerts: []*models.Alert{createAlertForDecision(httpBfDecisionCommunity)},
  438. decisions: []*models.Decision{httpBfDecisionCommunity, sshBfDecisionCommunity, sshBfDecisionCommunity, httpBfDecisionCommunity},
  439. },
  440. want: []*models.Alert{
  441. func() *models.Alert {
  442. a := createAlertForDecision(httpBfDecisionCommunity)
  443. a.Decisions = []*models.Decision{httpBfDecisionCommunity, sshBfDecisionCommunity, sshBfDecisionCommunity, httpBfDecisionCommunity}
  444. return a
  445. }(),
  446. },
  447. },
  448. {
  449. name: "List alert should pair up only with decisions having same scenario",
  450. args: args{
  451. alerts: []*models.Alert{createAlertForDecision(httpBfDecisionList), createAlertForDecision(sshBfDecisionList)},
  452. decisions: []*models.Decision{httpBfDecisionList, httpBfDecisionList, sshBfDecisionList, sshBfDecisionList},
  453. },
  454. want: []*models.Alert{
  455. func() *models.Alert {
  456. a := createAlertForDecision(httpBfDecisionList)
  457. a.Decisions = []*models.Decision{httpBfDecisionList, httpBfDecisionList}
  458. return a
  459. }(),
  460. func() *models.Alert {
  461. a := createAlertForDecision(sshBfDecisionList)
  462. a.Decisions = []*models.Decision{sshBfDecisionList, sshBfDecisionList}
  463. return a
  464. }(),
  465. },
  466. },
  467. }
  468. for _, tt := range tests {
  469. t.Run(tt.name, func(t *testing.T) {
  470. add_counters, _ := makeAddAndDeleteCounters()
  471. if got := fillAlertsWithDecisions(tt.args.alerts, tt.args.decisions, add_counters); !reflect.DeepEqual(got, tt.want) {
  472. t.Errorf("fillAlertsWithDecisions() = %v, want %v", got, tt.want)
  473. }
  474. })
  475. }
  476. }
  477. func TestAPICPullTop(t *testing.T) {
  478. api := getAPIC(t)
  479. api.dbClient.Ent.Decision.Create().
  480. SetOrigin(SCOPE_LISTS).
  481. SetType("ban").
  482. SetValue("9.9.9.9").
  483. SetScope("Ip").
  484. SetScenario("crowdsecurity/ssh-bf").
  485. SetUntil(time.Now().Add(time.Hour)).
  486. ExecX(context.Background())
  487. assertTotalDecisionCount(t, api.dbClient, 1)
  488. assertTotalValidDecisionCount(t, api.dbClient, 1)
  489. httpmock.Activate()
  490. defer httpmock.DeactivateAndReset()
  491. httpmock.RegisterResponder("GET", "http://api.crowdsec.net/api/decisions/stream", httpmock.NewBytesResponder(
  492. 200, jsonMarshalX(
  493. models.DecisionsStreamResponse{
  494. Deleted: models.GetDecisionsResponse{
  495. &models.Decision{
  496. Origin: &SCOPE_LISTS,
  497. Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
  498. Value: types.StrPtr("9.9.9.9"),
  499. Scope: types.StrPtr("Ip"),
  500. Duration: types.StrPtr("24h"),
  501. Type: types.StrPtr("ban"),
  502. }, // This is already present in DB
  503. &models.Decision{
  504. Origin: &SCOPE_LISTS,
  505. Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
  506. Value: types.StrPtr("9.1.9.9"),
  507. Scope: types.StrPtr("Ip"),
  508. Duration: types.StrPtr("24h"),
  509. Type: types.StrPtr("ban"),
  510. }, // This not present in DB.
  511. },
  512. New: models.GetDecisionsResponse{
  513. &models.Decision{
  514. Origin: &SCOPE_CAPI,
  515. Scenario: types.StrPtr("crowdsecurity/test1"),
  516. Value: types.StrPtr("1.2.3.4"),
  517. Scope: types.StrPtr("Ip"),
  518. Duration: types.StrPtr("24h"),
  519. Type: types.StrPtr("ban"),
  520. },
  521. &models.Decision{
  522. Origin: &SCOPE_CAPI,
  523. Scenario: types.StrPtr("crowdsecurity/test2"),
  524. Value: types.StrPtr("1.2.3.5"),
  525. Scope: types.StrPtr("Ip"),
  526. Duration: types.StrPtr("24h"),
  527. Type: types.StrPtr("ban"),
  528. }, // These two are from community list.
  529. &models.Decision{
  530. Origin: &SCOPE_LISTS,
  531. Scenario: types.StrPtr("crowdsecurity/http-bf"),
  532. Value: types.StrPtr("1.2.3.6"),
  533. Scope: types.StrPtr("Ip"),
  534. Duration: types.StrPtr("24h"),
  535. Type: types.StrPtr("ban"),
  536. },
  537. &models.Decision{
  538. Origin: &SCOPE_LISTS,
  539. Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
  540. Value: types.StrPtr("1.2.3.7"),
  541. Scope: types.StrPtr("Ip"),
  542. Duration: types.StrPtr("24h"),
  543. Type: types.StrPtr("ban"),
  544. }, // These two are from list subscription.
  545. },
  546. },
  547. ),
  548. ))
  549. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  550. if err != nil {
  551. t.Fatal(err)
  552. }
  553. apic, err := apiclient.NewDefaultClient(
  554. url,
  555. "/api",
  556. fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
  557. nil,
  558. )
  559. if err != nil {
  560. t.Fatal(err)
  561. }
  562. api.apiClient = apic
  563. err = api.PullTop()
  564. if err != nil {
  565. t.Fatal(err)
  566. }
  567. assertTotalDecisionCount(t, api.dbClient, 5)
  568. assertTotalValidDecisionCount(t, api.dbClient, 4)
  569. assertTotalAlertCount(t, api.dbClient, 3) // 2 for list sub , 1 for community list.
  570. alerts := api.dbClient.Ent.Alert.Query().AllX(context.Background())
  571. validDecisions := api.dbClient.Ent.Decision.Query().Where(
  572. decision.UntilGT(time.Now())).
  573. AllX(context.Background())
  574. decisionScenarioFreq := make(map[string]int)
  575. alertScenario := make(map[string]int)
  576. for _, alert := range alerts {
  577. alertScenario[alert.SourceScope]++
  578. }
  579. assert.Equal(t, len(alertScenario), 3)
  580. assert.Equal(t, alertScenario[SCOPE_CAPI_ALIAS], 1)
  581. assert.Equal(t, alertScenario["lists:crowdsecurity/ssh-bf"], 1)
  582. assert.Equal(t, alertScenario["lists:crowdsecurity/http-bf"], 1)
  583. for _, decisions := range validDecisions {
  584. decisionScenarioFreq[decisions.Scenario]++
  585. }
  586. assert.Equal(t, decisionScenarioFreq["crowdsecurity/http-bf"], 1)
  587. assert.Equal(t, decisionScenarioFreq["crowdsecurity/ssh-bf"], 1)
  588. assert.Equal(t, decisionScenarioFreq["crowdsecurity/test1"], 1)
  589. assert.Equal(t, decisionScenarioFreq["crowdsecurity/test2"], 1)
  590. }
  591. func TestAPICPush(t *testing.T) {
  592. testCases := []struct {
  593. name string
  594. alerts []*models.Alert
  595. expectedCalls int
  596. }{
  597. {
  598. name: "simple single alert",
  599. alerts: []*models.Alert{
  600. {
  601. Scenario: types.StrPtr("crowdsec/test"),
  602. ScenarioHash: types.StrPtr("certified"),
  603. ScenarioVersion: types.StrPtr("v1.0"),
  604. Simulated: types.BoolPtr(false),
  605. },
  606. },
  607. expectedCalls: 1,
  608. },
  609. {
  610. name: "simulated alert is not pushed",
  611. alerts: []*models.Alert{
  612. {
  613. Scenario: types.StrPtr("crowdsec/test"),
  614. ScenarioHash: types.StrPtr("certified"),
  615. ScenarioVersion: types.StrPtr("v1.0"),
  616. Simulated: types.BoolPtr(true),
  617. },
  618. },
  619. expectedCalls: 0,
  620. },
  621. {
  622. name: "1 request per 50 alerts",
  623. expectedCalls: 2,
  624. alerts: func() []*models.Alert {
  625. alerts := make([]*models.Alert, 100)
  626. for i := 0; i < 100; i++ {
  627. alerts[i] = &models.Alert{
  628. Scenario: types.StrPtr("crowdsec/test"),
  629. ScenarioHash: types.StrPtr("certified"),
  630. ScenarioVersion: types.StrPtr("v1.0"),
  631. Simulated: types.BoolPtr(false),
  632. }
  633. }
  634. return alerts
  635. }(),
  636. },
  637. }
  638. for _, testCase := range testCases {
  639. t.Run(testCase.name, func(t *testing.T) {
  640. api := getAPIC(t)
  641. api.pushInterval = time.Millisecond
  642. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  643. if err != nil {
  644. t.Fatal(err)
  645. }
  646. httpmock.Activate()
  647. defer httpmock.DeactivateAndReset()
  648. apic, err := apiclient.NewDefaultClient(
  649. url,
  650. "/api",
  651. fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
  652. nil,
  653. )
  654. if err != nil {
  655. t.Fatal(err)
  656. }
  657. api.apiClient = apic
  658. httpmock.RegisterResponder("POST", "http://api.crowdsec.net/api/signals", httpmock.NewBytesResponder(200, []byte{}))
  659. go func() {
  660. api.alertToPush <- testCase.alerts
  661. time.Sleep(time.Second)
  662. api.Shutdown()
  663. }()
  664. if err := api.Push(); err != nil {
  665. t.Fatal(err)
  666. }
  667. assert.Equal(t, httpmock.GetTotalCallCount(), testCase.expectedCalls)
  668. })
  669. }
  670. }
  671. func TestAPICSendMetrics(t *testing.T) {
  672. api := getAPIC(t)
  673. testCases := []struct {
  674. name string
  675. duration time.Duration
  676. expectedCalls int
  677. setUp func()
  678. metricsInterval time.Duration
  679. }{
  680. {
  681. name: "basic",
  682. duration: time.Millisecond * 5,
  683. metricsInterval: time.Millisecond,
  684. expectedCalls: 5,
  685. setUp: func() {},
  686. },
  687. {
  688. name: "with some metrics",
  689. duration: time.Millisecond * 5,
  690. metricsInterval: time.Millisecond,
  691. expectedCalls: 5,
  692. setUp: func() {
  693. api.dbClient.Ent.Machine.Create().
  694. SetMachineId("1234").
  695. SetPassword(testPassword.String()).
  696. SetIpAddress("1.2.3.4").
  697. SetScenarios("crowdsecurity/test").
  698. SetLastPush(time.Time{}).
  699. SetUpdatedAt(time.Time{}).
  700. ExecX(context.Background())
  701. api.dbClient.Ent.Bouncer.Create().
  702. SetIPAddress("1.2.3.6").
  703. SetName("someBouncer").
  704. SetAPIKey("foobar").
  705. SetRevoked(false).
  706. SetLastPull(time.Time{}).
  707. ExecX(context.Background())
  708. },
  709. },
  710. }
  711. for _, testCase := range testCases {
  712. t.Run(testCase.name, func(t *testing.T) {
  713. api = getAPIC(t)
  714. api.pushInterval = time.Millisecond
  715. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  716. if err != nil {
  717. t.Fatal(err)
  718. }
  719. httpmock.Activate()
  720. defer httpmock.DeactivateAndReset()
  721. apic, err := apiclient.NewDefaultClient(
  722. url,
  723. "/api",
  724. fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
  725. nil,
  726. )
  727. if err != nil {
  728. t.Fatal(err)
  729. }
  730. api.apiClient = apic
  731. api.metricsInterval = testCase.metricsInterval
  732. httpmock.RegisterNoResponder(httpmock.NewBytesResponder(200, []byte{}))
  733. testCase.setUp()
  734. go func() {
  735. if err := api.SendMetrics(); err != nil {
  736. panic(err)
  737. }
  738. }()
  739. time.Sleep(testCase.duration)
  740. assert.LessOrEqual(t, absDiff(testCase.expectedCalls, httpmock.GetTotalCallCount()), 2)
  741. })
  742. }
  743. }
  744. func TestAPICPull(t *testing.T) {
  745. api := getAPIC(t)
  746. testCases := []struct {
  747. name string
  748. setUp func()
  749. expectedDecisionCount int
  750. logContains string
  751. }{
  752. {
  753. name: "test pull if no scenarios are present",
  754. setUp: func() {},
  755. logContains: "scenario list is empty, will not pull yet",
  756. },
  757. {
  758. name: "test pull",
  759. setUp: func() {
  760. api.dbClient.Ent.Machine.Create().
  761. SetMachineId("1.2.3.4").
  762. SetPassword(testPassword.String()).
  763. SetIpAddress("1.2.3.4").
  764. SetScenarios("crowdsecurity/ssh-bf").
  765. ExecX(context.Background())
  766. },
  767. expectedDecisionCount: 1,
  768. },
  769. }
  770. for _, testCase := range testCases {
  771. t.Run(testCase.name, func(t *testing.T) {
  772. api = getAPIC(t)
  773. api.pullInterval = time.Millisecond
  774. url, err := url.ParseRequestURI("http://api.crowdsec.net/")
  775. if err != nil {
  776. t.Fatal(err)
  777. }
  778. httpmock.Activate()
  779. defer httpmock.DeactivateAndReset()
  780. apic, err := apiclient.NewDefaultClient(
  781. url,
  782. "/api",
  783. fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
  784. nil,
  785. )
  786. if err != nil {
  787. t.Fatal(err)
  788. }
  789. api.apiClient = apic
  790. httpmock.RegisterNoResponder(httpmock.NewBytesResponder(200, jsonMarshalX(
  791. models.DecisionsStreamResponse{
  792. New: models.GetDecisionsResponse{
  793. &models.Decision{
  794. Origin: &SCOPE_CAPI,
  795. Scenario: types.StrPtr("crowdsecurity/test2"),
  796. Value: types.StrPtr("1.2.3.5"),
  797. Scope: types.StrPtr("Ip"),
  798. Duration: types.StrPtr("24h"),
  799. Type: types.StrPtr("ban"),
  800. },
  801. },
  802. },
  803. )))
  804. testCase.setUp()
  805. var buf bytes.Buffer
  806. go func() {
  807. logrus.SetOutput(&buf)
  808. if err := api.Pull(); err != nil {
  809. panic(err)
  810. }
  811. }()
  812. time.Sleep(time.Millisecond * 10)
  813. logrus.SetOutput(os.Stderr)
  814. assert.Contains(t, buf.String(), testCase.logContains)
  815. assertTotalDecisionCount(t, api.dbClient, testCase.expectedDecisionCount)
  816. })
  817. }
  818. }
  819. func TestShouldShareAlert(t *testing.T) {
  820. testCases := []struct {
  821. name string
  822. consoleConfig *csconfig.ConsoleConfig
  823. alert *models.Alert
  824. expectedRet bool
  825. expectedTrust string
  826. }{
  827. {
  828. name: "custom alert should be shared if config enables it",
  829. consoleConfig: &csconfig.ConsoleConfig{
  830. ShareCustomScenarios: types.BoolPtr(true),
  831. },
  832. alert: &models.Alert{Simulated: types.BoolPtr(false)},
  833. expectedRet: true,
  834. expectedTrust: "custom",
  835. },
  836. {
  837. name: "custom alert should not be shared if config disables it",
  838. consoleConfig: &csconfig.ConsoleConfig{
  839. ShareCustomScenarios: types.BoolPtr(false),
  840. },
  841. alert: &models.Alert{Simulated: types.BoolPtr(false)},
  842. expectedRet: false,
  843. expectedTrust: "custom",
  844. },
  845. {
  846. name: "manual alert should be shared if config enables it",
  847. consoleConfig: &csconfig.ConsoleConfig{
  848. ShareManualDecisions: types.BoolPtr(true),
  849. },
  850. alert: &models.Alert{
  851. Simulated: types.BoolPtr(false),
  852. Decisions: []*models.Decision{{Origin: types.StrPtr("cscli")}},
  853. },
  854. expectedRet: true,
  855. expectedTrust: "manual",
  856. },
  857. {
  858. name: "manual alert should not be shared if config disables it",
  859. consoleConfig: &csconfig.ConsoleConfig{
  860. ShareManualDecisions: types.BoolPtr(false),
  861. },
  862. alert: &models.Alert{
  863. Simulated: types.BoolPtr(false),
  864. Decisions: []*models.Decision{{Origin: types.StrPtr("cscli")}},
  865. },
  866. expectedRet: false,
  867. expectedTrust: "manual",
  868. },
  869. {
  870. name: "manual alert should be shared if config enables it",
  871. consoleConfig: &csconfig.ConsoleConfig{
  872. ShareTaintedScenarios: types.BoolPtr(true),
  873. },
  874. alert: &models.Alert{
  875. Simulated: types.BoolPtr(false),
  876. ScenarioHash: types.StrPtr("whateverHash"),
  877. },
  878. expectedRet: true,
  879. expectedTrust: "tainted",
  880. },
  881. {
  882. name: "manual alert should not be shared if config disables it",
  883. consoleConfig: &csconfig.ConsoleConfig{
  884. ShareTaintedScenarios: types.BoolPtr(false),
  885. },
  886. alert: &models.Alert{
  887. Simulated: types.BoolPtr(false),
  888. ScenarioHash: types.StrPtr("whateverHash"),
  889. },
  890. expectedRet: false,
  891. expectedTrust: "tainted",
  892. },
  893. }
  894. for _, testCase := range testCases {
  895. t.Run(testCase.name, func(t *testing.T) {
  896. ret := shouldShareAlert(testCase.alert, testCase.consoleConfig)
  897. assert.Equal(t, ret, testCase.expectedRet)
  898. })
  899. }
  900. }