Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add explicit configuration for signals sharing and blocklists pull #3277

Merged
merged 11 commits into from
Nov 8, 2024
21 changes: 21 additions & 0 deletions cmd/crowdsec-cli/clicapi/capi.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,27 @@
fmt.Fprint(out, "Your instance is enrolled in the console\n")
}

switch *cfg.API.Server.OnlineClient.Sharing {
case true:
fmt.Fprint(out, "Sharing signals is enabled\n")
case false:
fmt.Fprint(out, "Sharing signals is disabled\n")

Check warning on line 232 in cmd/crowdsec-cli/clicapi/capi.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/clicapi/capi.go#L231-L232

Added lines #L231 - L232 were not covered by tests
}

switch *cfg.API.Server.OnlineClient.PullConfig.Community {
case true:
fmt.Fprint(out, "Pulling community blocklist is enabled\n")
case false:
fmt.Fprint(out, "Pulling community blocklist is disabled\n")

Check warning on line 239 in cmd/crowdsec-cli/clicapi/capi.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/clicapi/capi.go#L238-L239

Added lines #L238 - L239 were not covered by tests
}

switch *cfg.API.Server.OnlineClient.PullConfig.Blocklists {
case true:
fmt.Fprint(out, "Pulling blocklists from the console is enabled\n")
case false:
fmt.Fprint(out, "Pulling blocklists from the console is disabled\n")

Check warning on line 246 in cmd/crowdsec-cli/clicapi/capi.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/clicapi/capi.go#L245-L246

Added lines #L245 - L246 were not covered by tests
}

return nil
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/apiclient/decisions_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type DecisionsListOpts struct {

type DecisionsStreamOpts struct {
Startup bool `url:"startup,omitempty"`
CommunityPull bool `url:"community_pull"`
AdditionalPull bool `url:"additional_pull"`
Scopes string `url:"scopes,omitempty"`
ScenariosContaining string `url:"scenarios_containing,omitempty"`
ScenariosNotContaining string `url:"scenarios_not_containing,omitempty"`
Expand All @@ -43,6 +45,17 @@ func (o *DecisionsStreamOpts) addQueryParamsToURL(url string) (string, error) {
return "", err
}

//Those 2 are a bit different
//They default to true, and we only want to include them if they are false

if params.Get("community_pull") == "true" {
params.Del("community_pull")
}

if params.Get("additional_pull") == "true" {
params.Del("additional_pull")
}

return fmt.Sprintf("%s?%s", url, params.Encode()), nil
}

Expand Down
27 changes: 24 additions & 3 deletions pkg/apiclient/decisions_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"net/http"
"net/url"
"strings"
"testing"

log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -87,7 +88,7 @@ func TestDecisionsStream(t *testing.T) {
testMethod(t, r, http.MethodGet)

if r.Method == http.MethodGet {
if r.URL.RawQuery == "startup=true" {
if strings.Contains(r.URL.RawQuery, "startup=true") {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"deleted":null,"new":[{"duration":"3h59m55.756182786s","id":4,"origin":"cscli","scenario":"manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'","scope":"Ip","type":"ban","value":"1.2.3.4"}]}`))
} else {
Expand Down Expand Up @@ -160,7 +161,7 @@ func TestDecisionsStreamV3Compatibility(t *testing.T) {
testMethod(t, r, http.MethodGet)

if r.Method == http.MethodGet {
if r.URL.RawQuery == "startup=true" {
if strings.Contains(r.URL.RawQuery, "startup=true") {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"deleted":[{"scope":"ip","decisions":["1.2.3.5"]}],"new":[{"scope":"ip", "scenario": "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'", "decisions":[{"duration":"3h59m55.756182786s","value":"1.2.3.4"}]}]}`))
} else {
Expand Down Expand Up @@ -429,6 +430,8 @@ func TestDecisionsStreamOpts_addQueryParamsToURL(t *testing.T) {
Scopes string
ScenariosContaining string
ScenariosNotContaining string
CommunityPull bool
AdditionalPull bool
}

tests := []struct {
Expand All @@ -440,11 +443,17 @@ func TestDecisionsStreamOpts_addQueryParamsToURL(t *testing.T) {
{
name: "no filter",
expected: baseURLString + "?",
fields: fields{
CommunityPull: true,
AdditionalPull: true,
},
},
{
name: "startup=true",
fields: fields{
Startup: true,
Startup: true,
CommunityPull: true,
AdditionalPull: true,
},
expected: baseURLString + "?startup=true",
},
Expand All @@ -455,9 +464,19 @@ func TestDecisionsStreamOpts_addQueryParamsToURL(t *testing.T) {
Scopes: "ip,range",
ScenariosContaining: "ssh",
ScenariosNotContaining: "bf",
CommunityPull: true,
AdditionalPull: true,
},
expected: baseURLString + "?scenarios_containing=ssh&scenarios_not_containing=bf&scopes=ip%2Crange&startup=true",
},
{
name: "pull options",
fields: fields{
CommunityPull: false,
AdditionalPull: false,
},
expected: baseURLString + "?additional_pull=false&community_pull=false",
},
}

for _, tt := range tests {
Expand All @@ -467,6 +486,8 @@ func TestDecisionsStreamOpts_addQueryParamsToURL(t *testing.T) {
Scopes: tt.fields.Scopes,
ScenariosContaining: tt.fields.ScenariosContaining,
ScenariosNotContaining: tt.fields.ScenariosNotContaining,
CommunityPull: tt.fields.CommunityPull,
AdditionalPull: tt.fields.AdditionalPull,
}

got, err := o.addQueryParamsToURL(baseURLString)
Expand Down
54 changes: 36 additions & 18 deletions pkg/apiserver/apic.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@
consoleConfig *csconfig.ConsoleConfig
isPulling chan bool
whitelists *csconfig.CapiWhitelist

pullBlocklists bool
pullCommunity bool
shareSignals bool
}

// randomDuration returns a duration value between d-delta and d+delta
Expand Down Expand Up @@ -198,6 +202,9 @@
usageMetricsIntervalFirst: randomDuration(usageMetricsInterval, usageMetricsIntervalDelta),
isPulling: make(chan bool, 1),
whitelists: apicWhitelist,
pullBlocklists: *config.PullConfig.Blocklists,
pullCommunity: *config.PullConfig.Community,
shareSignals: *config.Sharing,
}

password := strfmt.Password(config.Credentials.Password)
Expand Down Expand Up @@ -295,7 +302,7 @@
var signals []*models.AddSignalsRequestItem

for _, alert := range alerts {
if ok := shouldShareAlert(alert, a.consoleConfig); ok {
if ok := shouldShareAlert(alert, a.consoleConfig, a.shareSignals); ok {
signals = append(signals, alertToSignal(alert, getScenarioTrustOfAlert(alert), *a.consoleConfig.ShareContext))
}
}
Expand Down Expand Up @@ -324,7 +331,13 @@
return scenarioTrust
}

func shouldShareAlert(alert *models.Alert, consoleConfig *csconfig.ConsoleConfig) bool {
func shouldShareAlert(alert *models.Alert, consoleConfig *csconfig.ConsoleConfig, shareSignals bool) bool {

if !shareSignals {
log.Debugf("sharing signals is disabled")
return false
}

if *alert.Simulated {
log.Debugf("simulation enabled for alert (id:%d), will not be sent to CAPI", alert.ID)
return false
Expand Down Expand Up @@ -625,7 +638,9 @@

log.Infof("Starting community-blocklist update")

data, _, err := a.apiClient.Decisions.GetStreamV3(ctx, apiclient.DecisionsStreamOpts{Startup: a.startup})
log.Debugf("Community pull: %t | Blocklist pull: %t", a.pullCommunity, a.pullBlocklists)

data, _, err := a.apiClient.Decisions.GetStreamV3(ctx, apiclient.DecisionsStreamOpts{Startup: a.startup, CommunityPull: a.pullCommunity, AdditionalPull: a.pullBlocklists})
if err != nil {
return fmt.Errorf("get stream: %w", err)
}
Expand All @@ -650,23 +665,26 @@

log.Printf("capi/community-blocklist : %d explicit deletions", nbDeleted)

if len(data.New) == 0 {
log.Infof("capi/community-blocklist : received 0 new entries (expected if you just installed crowdsec)")
return nil
}
if len(data.New) > 0 {
// create one alert for community blocklist using the first decision
decisions := a.apiClient.Decisions.GetDecisionsFromGroups(data.New)
// apply APIC specific whitelists
decisions = a.ApplyApicWhitelists(decisions)

// create one alert for community blocklist using the first decision
decisions := a.apiClient.Decisions.GetDecisionsFromGroups(data.New)
// apply APIC specific whitelists
decisions = a.ApplyApicWhitelists(decisions)
alert := createAlertForDecision(decisions[0])
alertsFromCapi := []*models.Alert{alert}
alertsFromCapi = fillAlertsWithDecisions(alertsFromCapi, decisions, addCounters)

alert := createAlertForDecision(decisions[0])
alertsFromCapi := []*models.Alert{alert}
alertsFromCapi = fillAlertsWithDecisions(alertsFromCapi, decisions, addCounters)

err = a.SaveAlerts(ctx, alertsFromCapi, addCounters, deleteCounters)
if err != nil {
return fmt.Errorf("while saving alerts: %w", err)
err = a.SaveAlerts(ctx, alertsFromCapi, addCounters, deleteCounters)
if err != nil {
return fmt.Errorf("while saving alerts: %w", err)
}
} else {
if a.pullCommunity {
log.Info("capi/community-blocklist : received 0 new entries (expected if you just installed crowdsec)")
} else {
log.Debug("capi/community-blocklist : community blocklist pull is disabled")
}

Check warning on line 687 in pkg/apiserver/apic.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/apic.go#L680-L687

Added lines #L680 - L687 were not covered by tests
}

// update blocklists
Expand Down
32 changes: 30 additions & 2 deletions pkg/apiserver/apic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ func getAPIC(t *testing.T, ctx context.Context) *apic {
ShareCustomScenarios: ptr.Of(false),
ShareContext: ptr.Of(false),
},
isPulling: make(chan bool, 1),
isPulling: make(chan bool, 1),
shareSignals: true,
pullBlocklists: true,
pullCommunity: true,
}
}

Expand Down Expand Up @@ -200,6 +203,11 @@ func TestNewAPIC(t *testing.T) {
Login: "foo",
Password: "bar",
},
Sharing: ptr.Of(true),
PullConfig: csconfig.CapiPullConfig{
Community: ptr.Of(true),
Blocklists: ptr.Of(true),
},
}
}

Expand Down Expand Up @@ -1193,6 +1201,7 @@ func TestShouldShareAlert(t *testing.T) {
tests := []struct {
name string
consoleConfig *csconfig.ConsoleConfig
shareSignals bool
alert *models.Alert
expectedRet bool
expectedTrust string
Expand All @@ -1203,6 +1212,7 @@ func TestShouldShareAlert(t *testing.T) {
ShareCustomScenarios: ptr.Of(true),
},
alert: &models.Alert{Simulated: ptr.Of(false)},
shareSignals: true,
expectedRet: true,
expectedTrust: "custom",
},
Expand All @@ -1212,6 +1222,7 @@ func TestShouldShareAlert(t *testing.T) {
ShareCustomScenarios: ptr.Of(false),
},
alert: &models.Alert{Simulated: ptr.Of(false)},
shareSignals: true,
expectedRet: false,
expectedTrust: "custom",
},
Expand All @@ -1220,6 +1231,7 @@ func TestShouldShareAlert(t *testing.T) {
consoleConfig: &csconfig.ConsoleConfig{
ShareManualDecisions: ptr.Of(true),
},
shareSignals: true,
alert: &models.Alert{
Simulated: ptr.Of(false),
Decisions: []*models.Decision{{Origin: ptr.Of(types.CscliOrigin)}},
Expand All @@ -1232,6 +1244,7 @@ func TestShouldShareAlert(t *testing.T) {
consoleConfig: &csconfig.ConsoleConfig{
ShareManualDecisions: ptr.Of(false),
},
shareSignals: true,
alert: &models.Alert{
Simulated: ptr.Of(false),
Decisions: []*models.Decision{{Origin: ptr.Of(types.CscliOrigin)}},
Expand All @@ -1244,6 +1257,7 @@ func TestShouldShareAlert(t *testing.T) {
consoleConfig: &csconfig.ConsoleConfig{
ShareTaintedScenarios: ptr.Of(true),
},
shareSignals: true,
alert: &models.Alert{
Simulated: ptr.Of(false),
ScenarioHash: ptr.Of("whateverHash"),
Expand All @@ -1256,18 +1270,32 @@ func TestShouldShareAlert(t *testing.T) {
consoleConfig: &csconfig.ConsoleConfig{
ShareTaintedScenarios: ptr.Of(false),
},
shareSignals: true,
alert: &models.Alert{
Simulated: ptr.Of(false),
ScenarioHash: ptr.Of("whateverHash"),
},
expectedRet: false,
expectedTrust: "tainted",
},
{
name: "manual alert should not be shared if global sharing is disabled",
consoleConfig: &csconfig.ConsoleConfig{
ShareManualDecisions: ptr.Of(true),
},
shareSignals: false,
alert: &models.Alert{
Simulated: ptr.Of(false),
ScenarioHash: ptr.Of("whateverHash"),
},
expectedRet: false,
expectedTrust: "manual",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ret := shouldShareAlert(tc.alert, tc.consoleConfig)
ret := shouldShareAlert(tc.alert, tc.consoleConfig, tc.shareSignals)
assert.Equal(t, tc.expectedRet, ret)
})
}
Expand Down
24 changes: 23 additions & 1 deletion pkg/csconfig/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,17 @@ type ApiCredentialsCfg struct {
CertPath string `yaml:"cert_path,omitempty"`
}

/*global api config (for lapi->oapi)*/
type CapiPullConfig struct {
Community *bool `yaml:"community,omitempty"`
Blocklists *bool `yaml:"blocklists,omitempty"`
}

/*global api config (for lapi->capi)*/
type OnlineApiClientCfg struct {
CredentialsFilePath string `yaml:"credentials_path,omitempty"` // credz will be edited by software, store in diff file
Credentials *ApiCredentialsCfg `yaml:"-"`
PullConfig CapiPullConfig `yaml:"pull,omitempty"`
Sharing *bool `yaml:"sharing,omitempty"`
}

/*local api config (for crowdsec/cscli->lapi)*/
Expand Down Expand Up @@ -344,6 +351,21 @@ func (c *Config) LoadAPIServer(inCli bool) error {
log.Printf("push and pull to Central API disabled")
}

//Set default values for CAPI push/pull
if c.API.Server.OnlineClient != nil {
if c.API.Server.OnlineClient.PullConfig.Community == nil {
c.API.Server.OnlineClient.PullConfig.Community = ptr.Of(true)
}

if c.API.Server.OnlineClient.PullConfig.Blocklists == nil {
c.API.Server.OnlineClient.PullConfig.Blocklists = ptr.Of(true)
}

if c.API.Server.OnlineClient.Sharing == nil {
c.API.Server.OnlineClient.Sharing = ptr.Of(true)
}
}

if err := c.LoadDBConfig(inCli); err != nil {
return err
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/csconfig/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ func TestLoadAPIServer(t *testing.T) {
Login: "test",
Password: "testpassword",
},
Sharing: ptr.Of(true),
PullConfig: CapiPullConfig{
Community: ptr.Of(true),
Blocklists: ptr.Of(true),
},
},
Profiles: tmpLAPI.Profiles,
ProfilesPath: "./testdata/profiles.yaml",
Expand Down
Loading