github.com/crowdsecurity/crowdsec@v1.6.1/pkg/apiserver/apic_test.go (about)

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