apic_test.go 25 KB

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