Skip to content

Commit

Permalink
Add explicit configuration for signals sharing and blocklists pull (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
blotus authored Nov 8, 2024
1 parent 94a2a58 commit 5d414f5
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 24 deletions.
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 @@ func (cli *cliCapi) Status(ctx context.Context, out io.Writer, hub *cwhub.Hub) e
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")
}

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")
}

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")
}

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 @@ type apic struct {
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 @@ func NewAPIC(ctx context.Context, config *csconfig.OnlineApiClientCfg, dbClient
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 @@ func (a *apic) Push(ctx context.Context) error {
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 @@ func getScenarioTrustOfAlert(alert *models.Alert) string {
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 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {

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 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {

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")
}
}

// 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

0 comments on commit 5d414f5

Please sign in to comment.