lapi to capi : allow push of tainted/custom/manual decisions (#1154)

* add console command to control signal sharing
* modify metrics endpoint to add lastpush

Co-authored-by: alteredCoder <kevin@crowdsec.net>
This commit is contained in:
Thibault "bui" Koechlin 2022-01-13 16:46:16 +01:00 committed by GitHub
parent 50fb1e3df1
commit 6e92da76ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 851 additions and 81 deletions

View file

@ -2,13 +2,22 @@ package main
import (
"context"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io/fs"
"net/url"
"os"
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
"github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/enescakir/emoji"
"github.com/go-openapi/strfmt"
"github.com/olekukonko/tablewriter"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -21,12 +30,24 @@ func NewConsoleCmd() *cobra.Command {
DisableAutoGenTag: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
var fdErr *fs.PathError
if errors.As(err, &fdErr) {
log.Fatalf("Unable to load Local API : %s", fdErr)
} else if err != nil {
log.Fatalf("Unable to load required Local API Configuration : %s", err)
} else {
log.Fatal("Local API is disabled, please run this command on the local API machine")
}
}
if csConfig.DisableAPI {
log.Fatal("Local API is disabled, please run this command on the local API machine")
}
if csConfig.API.Server.OnlineClient == nil {
log.Fatalf("no configuration for Central API (CAPI) in '%s'", *csConfig.FilePath)
log.Fatalf("No configuration for Central API (CAPI) in '%s'", *csConfig.FilePath)
}
if csConfig.API.Server.OnlineClient.Credentials == nil {
log.Fatal("You must configure Central API (CAPI) with `cscli capi register` before enrolling your instance")
}
return nil
},
}
@ -48,18 +69,6 @@ After running this command your will need to validate the enrollment in the weba
`,
Args: cobra.ExactArgs(1),
DisableAutoGenTag: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
log.Fatal("Local API is disabled, please run this command on the local API machine")
}
if csConfig.API.Server.OnlineClient == nil {
log.Fatalf("no configuration for Central API (CAPI) in '%s'", *csConfig.FilePath)
}
if csConfig.API.Server.OnlineClient.Credentials == nil {
log.Fatal("You must configure Central API (CAPI) with `cscli capi register` before enrolling your instance")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
password := strfmt.Password(csConfig.API.Server.OnlineClient.Credentials.Password)
apiURL, err := url.Parse(csConfig.API.Server.OnlineClient.Credentials.URL)
@ -97,11 +106,193 @@ After running this command your will need to validate the enrollment in the weba
if err != nil {
log.Fatalf("Could not enroll instance: %s", err)
}
SetConsoleOpts(csconfig.CONSOLE_CONFIGS, true)
log.Infof("Enabled tainted&manual alerts sharing, see 'cscli console status'.")
log.Infof("Watcher successfully enrolled. Visit https://app.crowdsec.net to accept it.")
log.Infof("Please restart crowdsec after accepting the enrollment.")
},
}
cmdEnroll.Flags().StringVarP(&name, "name", "n", "", "Name to display in the console")
cmdEnroll.Flags().StringSliceVarP(&tags, "tags", "t", tags, "Tags to display in the console")
cmdConsole.AddCommand(cmdEnroll)
var enableAll, disableAll bool
cmdEnable := &cobra.Command{
Use: "enable [feature-flag]",
Short: "Enable a feature flag",
Example: "enable alerts-tainted",
Long: `
Enable given information push to the central API. Allows to empower the console`,
ValidArgs: csconfig.CONSOLE_CONFIGS,
Args: cobra.MinimumNArgs(1),
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
if enableAll {
SetConsoleOpts(csconfig.CONSOLE_CONFIGS, true)
} else {
SetConsoleOpts(args, true)
}
if err := csConfig.API.Server.DumpConsoleConfig(); err != nil {
log.Fatalf("failed writing console config : %s", err)
}
if enableAll {
log.Infof("All features have been enabled successfully")
} else {
log.Infof("%v have been enabled", args)
}
log.Infof(ReloadMessage())
},
}
cmdEnable.Flags().BoolVarP(&enableAll, "all", "a", false, "Enable all feature flags")
cmdConsole.AddCommand(cmdEnable)
cmdDisable := &cobra.Command{
Use: "disable [feature-flag]",
Short: "Disable a feature flag",
Example: "disable alerts-tainted",
Long: `
Disable given information push to the central API.`,
ValidArgs: csconfig.CONSOLE_CONFIGS,
Args: cobra.MinimumNArgs(1),
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
if disableAll {
SetConsoleOpts(csconfig.CONSOLE_CONFIGS, false)
} else {
SetConsoleOpts(args, false)
}
if err := csConfig.API.Server.DumpConsoleConfig(); err != nil {
log.Fatalf("failed writing console config : %s", err)
}
if disableAll {
log.Infof("All features have been disabled")
} else {
log.Infof("%v have been disabled", args)
}
log.Infof(ReloadMessage())
},
}
cmdDisable.Flags().BoolVarP(&disableAll, "all", "a", false, "Enable all feature flags")
cmdConsole.AddCommand(cmdDisable)
cmdConsoleStatus := &cobra.Command{
Use: "status [feature-flag]",
Short: "Shows status of one or all feature flags",
Example: "status alerts-tainted",
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
switch csConfig.Cscli.Output {
case "human":
table := tablewriter.NewWriter(os.Stdout)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetHeader([]string{"Option Name", "Activated", "Description"})
for _, option := range csconfig.CONSOLE_CONFIGS {
switch option {
case csconfig.SEND_CUSTOM_SCENARIOS:
activated := string(emoji.CrossMark)
if *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios {
activated = string(emoji.CheckMarkButton)
}
table.Append([]string{option, activated, "Send alerts from custom scenarios to the console"})
case csconfig.SEND_MANUAL_SCENARIOS:
activated := string(emoji.CrossMark)
if *csConfig.API.Server.ConsoleConfig.ShareManualDecisions {
activated = string(emoji.CheckMarkButton)
}
table.Append([]string{option, activated, "Send manual decisions to the console"})
case csconfig.SEND_TAINTED_SCENARIOS:
activated := string(emoji.CrossMark)
if *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios {
activated = string(emoji.CheckMarkButton)
}
table.Append([]string{option, activated, "Send alerts from tainted scenarios to the console"})
}
}
table.Render()
case "json":
data, err := json.MarshalIndent(csConfig.API.Server.ConsoleConfig, "", " ")
if err != nil {
log.Fatalf("failed to marshal configuration: %s", err)
}
fmt.Printf("%s\n", string(data))
case "raw":
csvwriter := csv.NewWriter(os.Stdout)
err := csvwriter.Write([]string{"option", "enabled"})
if err != nil {
log.Fatal(err)
}
rows := [][]string{
{"share_manual_decisions", fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareManualDecisions)},
{"share_custom", fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios)},
{"share_tainted", fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios)},
}
for _, row := range rows {
err = csvwriter.Write(row)
if err != nil {
log.Fatal(err)
}
}
csvwriter.Flush()
}
},
}
cmdConsole.AddCommand(cmdConsoleStatus)
return cmdConsole
}
func SetConsoleOpts(args []string, wanted bool) {
for _, arg := range args {
switch arg {
case csconfig.SEND_CUSTOM_SCENARIOS:
/*for each flag check if it's already set before setting it*/
if csConfig.API.Server.ConsoleConfig.ShareCustomScenarios != nil {
if *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios == wanted {
log.Infof("%s already set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted)
} else {
log.Infof("%s set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted)
*csConfig.API.Server.ConsoleConfig.ShareCustomScenarios = wanted
}
} else {
log.Infof("%s set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted)
csConfig.API.Server.ConsoleConfig.ShareCustomScenarios = types.BoolPtr(wanted)
}
case csconfig.SEND_TAINTED_SCENARIOS:
/*for each flag check if it's already set before setting it*/
if csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios != nil {
if *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios == wanted {
log.Infof("%s already set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted)
} else {
log.Infof("%s set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted)
*csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios = wanted
}
} else {
log.Infof("%s set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted)
csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios = types.BoolPtr(wanted)
}
case csconfig.SEND_MANUAL_SCENARIOS:
/*for each flag check if it's already set before setting it*/
if csConfig.API.Server.ConsoleConfig.ShareManualDecisions != nil {
if *csConfig.API.Server.ConsoleConfig.ShareManualDecisions == wanted {
log.Infof("%s already set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted)
} else {
log.Infof("%s set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted)
*csConfig.API.Server.ConsoleConfig.ShareManualDecisions = wanted
}
} else {
log.Infof("%s set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted)
csConfig.API.Server.ConsoleConfig.ShareManualDecisions = types.BoolPtr(wanted)
}
default:
log.Fatalf("unknown flag %s", arg)
}
}
}

View file

@ -37,8 +37,7 @@ func DecisionsToTable(alerts *models.GetAlertsResponse) error {
var spamLimit map[string]bool = make(map[string]bool)
var skipped = 0
/*process in reverse order to keep the latest item only*/
for aIdx := len(*alerts) - 1; aIdx >= 0; aIdx-- {
for aIdx := 0; aIdx < len(*alerts); aIdx++ {
alertItem := (*alerts)[aIdx]
newDecisions := make([]*models.Decision, 0)
for _, decisionItem := range alertItem.Decisions {
@ -303,7 +302,7 @@ cscli decisions add --scope username --value foobar
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
var err error
var ip, ipRange string
var ipRange string
alerts := models.AddAlertsRequest{}
origin := "cscli"
capacity := int32(0)
@ -358,7 +357,7 @@ cscli decisions add --scope username --value foobar
AsName: empty,
AsNumber: empty,
Cn: empty,
IP: ip,
IP: addValue,
Range: ipRange,
Scope: &addScope,
Value: &addValue,

View file

@ -41,6 +41,7 @@ api:
log_level: info
listen_uri: 127.0.0.1:8080
profiles_path: /etc/crowdsec/profiles.yaml
console_path: /etc/crowdsec/console_config.yaml
online_client: # Central API credentials (to push signals and receive bad IPs)
credentials_path: /etc/crowdsec/online_api_credentials.yaml
# tls:

View file

@ -0,0 +1,3 @@
share_manual_decisions: false
share_custom: true
share_tainted: true

1
debian/rules vendored
View file

@ -47,4 +47,5 @@ override_dh_auto_install:
cp config/config.yaml debian/crowdsec/etc/crowdsec/config.yaml
cp config/simulation.yaml debian/crowdsec/etc/crowdsec/simulation.yaml
cp config/profiles.yaml debian/crowdsec/etc/crowdsec/profiles.yaml
cp config/console_config.yaml debian/crowdsec/etc/crowdsec/console_config.yaml
cp -a config/patterns debian/crowdsec/etc/crowdsec

2
go.sum
View file

@ -204,6 +204,7 @@ github.com/go-openapi/errors v0.19.9 h1:9SnKdGhiPZHF3ttwFMiCBEb8jQ4IDdrK+5+a0oTy
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.1 h1:j23mMDtRxMwIobkpId7sWh7Ddcx4ivaoqUbfXx5P+a8=
github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
@ -939,6 +940,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -3,7 +3,8 @@ package apiclient
import (
"context"
"fmt"
"log"
log "github.com/sirupsen/logrus"
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/pkg/errors"
@ -24,6 +25,10 @@ func (s *SignalService) Add(ctx context.Context, signals *models.AddSignalsReque
if err != nil {
return nil, resp, errors.Wrap(err, "while performing request")
}
log.Printf("Signal push response : http %s", resp.Response.Status)
if resp.Response.StatusCode != 200 {
log.Warnf("Signal push response : http %s", resp.Response.Status)
} else {
log.Debugf("Signal push response : http %s", resp.Response.Status)
}
return &response, resp, nil
}

View file

@ -44,6 +44,7 @@ type apic struct {
startup bool
credentials *csconfig.ApiCredentialsCfg
scenarioList []string
consoleConfig *csconfig.ConsoleConfig
}
func IsInSlice(a string, b []string) bool {
@ -75,7 +76,7 @@ func (a *apic) FetchScenariosListFromDB() ([]string, error) {
return scenarios, nil
}
func AlertToSignal(alert *models.Alert) *models.AddSignalsRequestItem {
func AlertToSignal(alert *models.Alert, scenarioTrust string) *models.AddSignalsRequestItem {
return &models.AddSignalsRequestItem{
Message: alert.Message,
Scenario: alert.Scenario,
@ -86,21 +87,23 @@ func AlertToSignal(alert *models.Alert) *models.AddSignalsRequestItem {
StopAt: alert.StopAt,
CreatedAt: alert.CreatedAt,
MachineID: alert.MachineID,
ScenarioTrust: &scenarioTrust,
}
}
func NewAPIC(config *csconfig.OnlineApiClientCfg, dbClient *database.Client) (*apic, error) {
func NewAPIC(config *csconfig.OnlineApiClientCfg, dbClient *database.Client, consoleConfig *csconfig.ConsoleConfig) (*apic, error) {
var err error
ret := &apic{
alertToPush: make(chan []*models.Alert),
dbClient: dbClient,
mu: sync.Mutex{},
startup: true,
credentials: config.Credentials,
pullTomb: tomb.Tomb{},
pushTomb: tomb.Tomb{},
metricsTomb: tomb.Tomb{},
scenarioList: make([]string, 0),
alertToPush: make(chan []*models.Alert),
dbClient: dbClient,
mu: sync.Mutex{},
startup: true,
credentials: config.Credentials,
pullTomb: tomb.Tomb{},
pushTomb: tomb.Tomb{},
metricsTomb: tomb.Tomb{},
scenarioList: make([]string, 0),
consoleConfig: consoleConfig,
}
ret.pullInterval, err = time.ParseDuration(PullInterval)
@ -167,20 +170,39 @@ func (a *apic) Push() error {
case alerts := <-a.alertToPush:
var signals []*models.AddSignalsRequestItem
for _, alert := range alerts {
/*we're only interested into decisions coming from scenarios of the hub*/
if alert.ScenarioHash == nil || *alert.ScenarioHash == "" {
continue
}
/*and we're not interested into tainted scenarios neither*/
if alert.ScenarioVersion == nil || *alert.ScenarioVersion == "" || *alert.ScenarioVersion == "?" {
continue
}
/*we also ignore alerts in simulated mode*/
if *alert.Simulated {
log.Debugf("simulation enabled for alert (id:%d), will not be sent to CAPI", alert.ID)
continue
}
signals = append(signals, AlertToSignal(alert))
scenarioTrust := "certified"
if alert.ScenarioHash == nil || *alert.ScenarioHash == "" {
scenarioTrust = "custom"
} else if alert.ScenarioVersion == nil || *alert.ScenarioVersion == "" || *alert.ScenarioVersion == "?" {
scenarioTrust = "tainted"
}
if len(alert.Decisions) > 0 {
if *alert.Decisions[0].Origin == "cscli" {
scenarioTrust = "manual"
}
}
switch scenarioTrust {
case "manual":
if !*a.consoleConfig.ShareManualDecisions {
log.Debugf("manual decision generated an alert, doesn't send it to CAPI because options is disabled")
continue
}
case "tainted":
if !*a.consoleConfig.ShareTaintedScenarios {
log.Debugf("tainted scenario generated an alert, doesn't send it to CAPI because options is disabled")
continue
}
case "custom":
if !*a.consoleConfig.ShareCustomScenarios {
log.Debugf("custom scenario generated an alert, doesn't send it to CAPI because options is disabled")
continue
}
}
signals = append(signals, AlertToSignal(alert, scenarioTrust))
}
a.mu.Lock()
cache = append(cache, signals...)
@ -477,8 +499,8 @@ func (a *apic) GetMetrics() (*models.Metrics, error) {
version := cwversion.VersionStr()
metric := &models.Metrics{
ApilVersion: &version,
Machines: make([]*models.MetricsSoftInfo, 0),
Bouncers: make([]*models.MetricsSoftInfo, 0),
Machines: make([]*models.MetricsAgentInfo, 0),
Bouncers: make([]*models.MetricsBouncerInfo, 0),
}
machines, err := a.dbClient.ListMachines()
if err != nil {
@ -489,17 +511,21 @@ func (a *apic) GetMetrics() (*models.Metrics, error) {
return metric, err
}
for _, machine := range machines {
m := &models.MetricsSoftInfo{
Version: machine.Version,
Name: machine.MachineId,
m := &models.MetricsAgentInfo{
Version: machine.Version,
Name: machine.MachineId,
LastUpdate: machine.UpdatedAt.String(),
LastPush: machine.LastPush.String(),
}
metric.Machines = append(metric.Machines, m)
}
for _, bouncer := range bouncers {
m := &models.MetricsSoftInfo{
Version: bouncer.Version,
Name: bouncer.Type,
m := &models.MetricsBouncerInfo{
Version: bouncer.Version,
CustomName: bouncer.Name,
Name: bouncer.Type,
LastPull: bouncer.LastPull.String(),
}
metric.Bouncers = append(metric.Bouncers, m)
}

View file

@ -38,6 +38,7 @@ type APIServer struct {
httpServer *http.Server
apic *apic
httpServerTomb tomb.Tomb
consoleConfig *csconfig.ConsoleConfig
}
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
@ -165,19 +166,21 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
return
})
router.Use(CustomRecoveryWithWriter())
controller := &controllers.Controller{
DBClient: dbClient,
Ectx: context.Background(),
Router: router,
Profiles: config.Profiles,
Log: clog,
DBClient: dbClient,
Ectx: context.Background(),
Router: router,
Profiles: config.Profiles,
Log: clog,
ConsoleConfig: config.ConsoleConfig,
}
var apiClient *apic
if config.OnlineClient != nil && config.OnlineClient.Credentials != nil {
log.Printf("Loading CAPI pusher")
apiClient, err = NewAPIC(config.OnlineClient, dbClient)
apiClient, err = NewAPIC(config.OnlineClient, dbClient, config.ConsoleConfig)
if err != nil {
return &APIServer{}, err
}
@ -197,6 +200,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
router: router,
apic: apiClient,
httpServerTomb: tomb.Tomb{},
consoleConfig: config.ConsoleConfig,
}, nil
}

View file

@ -49,6 +49,11 @@ func LoadTestConfig() csconfig.Config {
ListenURI: "http://127.0.0.1:8080",
DbConfig: &dbconfig,
ProfilesPath: "./tests/profiles.yaml",
ConsoleConfig: &csconfig.ConsoleConfig{
ShareManualDecisions: new(bool),
ShareTaintedScenarios: new(bool),
ShareCustomScenarios: new(bool),
},
}
apiConfig := csconfig.APICfg{
Server: &apiServerConfig,
@ -76,6 +81,11 @@ func LoadTestConfigForwardedFor() csconfig.Config {
DbConfig: &dbconfig,
ProfilesPath: "./tests/profiles.yaml",
UseForwardedForHeaders: true,
ConsoleConfig: &csconfig.ConsoleConfig{
ShareManualDecisions: new(bool),
ShareTaintedScenarios: new(bool),
ShareCustomScenarios: new(bool),
},
}
apiConfig := csconfig.APICfg{
Server: &apiServerConfig,

View file

@ -2,6 +2,8 @@ package controllers
import (
"context"
"net/http"
"github.com/alexliesenfeld/health"
v1 "github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers/v1"
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
@ -10,7 +12,6 @@ import (
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"net/http"
)
type Controller struct {
@ -21,6 +22,7 @@ type Controller struct {
CAPIChan chan []*models.Alert
PluginChannel chan csplugin.ProfileAlert
Log *log.Logger
ConsoleConfig *csconfig.ConsoleConfig
}
func (c *Controller) Init() error {
@ -51,7 +53,7 @@ func serveHealth() http.HandlerFunc {
}
func (c *Controller) NewV1() error {
handlerV1, err := v1.New(c.DBClient, c.Ectx, c.Profiles, c.CAPIChan, c.PluginChannel)
handlerV1, err := v1.New(c.DBClient, c.Ectx, c.Profiles, c.CAPIChan, c.PluginChannel, *c.ConsoleConfig)
if err != nil {
return err
}

View file

@ -156,10 +156,14 @@ func (c *Controller) CreateAlert(gctx *gin.Context) {
gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}
if !matched {
continue
}
alert.Decisions = append(alert.Decisions, profileDecisions...)
if len(alert.Decisions) == 0 { // non manual decision
alert.Decisions = append(alert.Decisions, profileDecisions...)
}
profileAlert := *alert
c.sendAlertToPluginChannel(&profileAlert, uint(pIdx))
if profile.OnSuccess == "break" {

View file

@ -18,9 +18,10 @@ type Controller struct {
Profiles []*csconfig.ProfileCfg
CAPIChan chan []*models.Alert
PluginChannel chan csplugin.ProfileAlert
ConsoleConfig csconfig.ConsoleConfig
}
func New(dbClient *database.Client, ctx context.Context, profiles []*csconfig.ProfileCfg, capiChan chan []*models.Alert, pluginChannel chan csplugin.ProfileAlert) (*Controller, error) {
func New(dbClient *database.Client, ctx context.Context, profiles []*csconfig.ProfileCfg, capiChan chan []*models.Alert, pluginChannel chan csplugin.ProfileAlert, consoleConfig csconfig.ConsoleConfig) (*Controller, error) {
var err error
v1 := &Controller{
Ectx: ctx,
@ -29,6 +30,7 @@ func New(dbClient *database.Client, ctx context.Context, profiles []*csconfig.Pr
Profiles: profiles,
CAPIChan: capiChan,
PluginChannel: pluginChannel,
ConsoleConfig: consoleConfig,
}
v1.Middlewares, err = middlewares.NewMiddlewares(dbClient)
if err != nil {

View file

@ -85,6 +85,8 @@ type LocalApiServerCfg struct {
LogMedia string `yaml:"-"`
OnlineClient *OnlineApiClientCfg `yaml:"online_client"`
ProfilesPath string `yaml:"profiles_path,omitempty"`
ConsoleConfigPath string `yaml:"console_path,omitempty"`
ConsoleConfig *ConsoleConfig `yaml:"-"`
Profiles []*ProfileCfg `yaml:"-"`
LogLevel *log.Level `yaml:"log_level"`
UseForwardedForHeaders bool `yaml:"use_forwarded_for_headers,omitempty"`
@ -114,6 +116,13 @@ func (c *Config) LoadAPIServer() error {
if err := c.API.Server.LoadProfiles(); err != nil {
return errors.Wrap(err, "while loading profiles for LAPI")
}
if c.API.Server.ConsoleConfigPath == "" {
c.API.Server.ConsoleConfigPath = DefaultConsoleConfgFilePath
}
if err := c.API.Server.LoadConsoleConfig(); err != nil {
return errors.Wrap(err, "while loading console options")
}
if c.API.Server.OnlineClient != nil && c.API.Server.OnlineClient.CredentialsFilePath != "" {
if err := c.API.Server.OnlineClient.Load(); err != nil {
return errors.Wrap(err, "loading online client credentials")

View file

@ -8,6 +8,7 @@ import (
"strings"
"testing"
"github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
)
@ -206,6 +207,12 @@ func TestLoadAPIServer(t *testing.T) {
DbPath: "./tests/test.db",
Type: "sqlite",
},
ConsoleConfigPath: "/etc/crowdsec/console_config.yaml",
ConsoleConfig: &ConsoleConfig{
ShareManualDecisions: types.BoolPtr(false),
ShareTaintedScenarios: types.BoolPtr(true),
ShareCustomScenarios: types.BoolPtr(true),
},
LogDir: LogDirFullPath,
LogMedia: "stdout",
OnlineClient: &OnlineApiClientCfg{

83
pkg/csconfig/console.go Normal file
View file

@ -0,0 +1,83 @@
package csconfig
import (
"fmt"
"io/ioutil"
"os"
"github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)
const (
SEND_CUSTOM_SCENARIOS = "custom"
SEND_TAINTED_SCENARIOS = "tainted"
SEND_MANUAL_SCENARIOS = "manual"
)
var DefaultConsoleConfgFilePath = "/etc/crowdsec/console_config.yaml"
var CONSOLE_CONFIGS = []string{SEND_CUSTOM_SCENARIOS, SEND_MANUAL_SCENARIOS, SEND_TAINTED_SCENARIOS}
type ConsoleConfig struct {
ShareManualDecisions *bool `yaml:"share_manual_decisions"`
ShareTaintedScenarios *bool `yaml:"share_tainted"`
ShareCustomScenarios *bool `yaml:"share_custom"`
}
func (c *LocalApiServerCfg) LoadConsoleConfig() error {
c.ConsoleConfig = &ConsoleConfig{}
if _, err := os.Stat(c.ConsoleConfigPath); err != nil && os.IsNotExist(err) {
log.Debugf("no console configuration to load")
c.ConsoleConfig.ShareCustomScenarios = types.BoolPtr(true)
c.ConsoleConfig.ShareTaintedScenarios = types.BoolPtr(true)
c.ConsoleConfig.ShareManualDecisions = types.BoolPtr(false)
return nil
}
yamlFile, err := ioutil.ReadFile(c.ConsoleConfigPath)
if err != nil {
return fmt.Errorf("reading console config file '%s': %s", c.ConsoleConfigPath, err)
}
err = yaml.Unmarshal(yamlFile, c.ConsoleConfig)
if err != nil {
return fmt.Errorf("unmarshaling console config file '%s': %s", c.ConsoleConfigPath, err)
}
if c.ConsoleConfig.ShareCustomScenarios == nil {
log.Debugf("no share_custom scenarios found, setting to true")
c.ConsoleConfig.ShareCustomScenarios = types.BoolPtr(true)
}
if c.ConsoleConfig.ShareTaintedScenarios == nil {
log.Debugf("no share_tainted scenarios found, setting to true")
c.ConsoleConfig.ShareTaintedScenarios = types.BoolPtr(true)
}
if c.ConsoleConfig.ShareManualDecisions == nil {
log.Debugf("no share_manual scenarios found, setting to false")
c.ConsoleConfig.ShareManualDecisions = types.BoolPtr(false)
}
log.Debugf("Console configuration '%s' loaded successfully", c.ConsoleConfigPath)
return nil
}
func (c *LocalApiServerCfg) DumpConsoleConfig() error {
var out []byte
var err error
if out, err = yaml.Marshal(c.ConsoleConfig); err != nil {
return errors.Wrapf(err, "while marshaling ConsoleConfig (for %s)", c.ConsoleConfigPath)
}
if c.ConsoleConfigPath == "" {
log.Debugf("Empty console_path, defaulting to %s", DefaultConsoleConfgFilePath)
c.ConsoleConfigPath = DefaultConsoleConfgFilePath
}
if err := os.WriteFile(c.ConsoleConfigPath, out, 0600); err != nil {
return errors.Wrapf(err, "while dumping console config to %s", c.ConsoleConfigPath)
}
return nil
}

View file

@ -15,7 +15,6 @@ import (
func BuildDecisionRequestWithFilter(query *ent.DecisionQuery, filter map[string][]string) (*ent.DecisionQuery, error) {
//func BuildDecisionRequestWithFilter(query *ent.Query, filter map[string][]string) (*ent.DecisionQuery, error) {
var err error
var start_ip, start_sfx, end_ip, end_sfx int64
var ip_sz int

View file

@ -20,6 +20,8 @@ type Machine struct {
CreatedAt time.Time `json:"created_at,omitempty"`
// UpdatedAt holds the value of the "updated_at" field.
UpdatedAt time.Time `json:"updated_at,omitempty"`
// LastPush holds the value of the "last_push" field.
LastPush time.Time `json:"last_push,omitempty"`
// MachineId holds the value of the "machineId" field.
MachineId string `json:"machineId,omitempty"`
// Password holds the value of the "password" field.
@ -68,7 +70,7 @@ func (*Machine) scanValues(columns []string) ([]interface{}, error) {
values[i] = new(sql.NullInt64)
case machine.FieldMachineId, machine.FieldPassword, machine.FieldIpAddress, machine.FieldScenarios, machine.FieldVersion, machine.FieldStatus:
values[i] = new(sql.NullString)
case machine.FieldCreatedAt, machine.FieldUpdatedAt:
case machine.FieldCreatedAt, machine.FieldUpdatedAt, machine.FieldLastPush:
values[i] = new(sql.NullTime)
default:
return nil, fmt.Errorf("unexpected column %q for type Machine", columns[i])
@ -103,6 +105,12 @@ func (m *Machine) assignValues(columns []string, values []interface{}) error {
} else if value.Valid {
m.UpdatedAt = value.Time
}
case machine.FieldLastPush:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field last_push", values[i])
} else if value.Valid {
m.LastPush = value.Time
}
case machine.FieldMachineId:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field machineId", values[i])
@ -182,6 +190,8 @@ func (m *Machine) String() string {
builder.WriteString(m.CreatedAt.Format(time.ANSIC))
builder.WriteString(", updated_at=")
builder.WriteString(m.UpdatedAt.Format(time.ANSIC))
builder.WriteString(", last_push=")
builder.WriteString(m.LastPush.Format(time.ANSIC))
builder.WriteString(", machineId=")
builder.WriteString(m.MachineId)
builder.WriteString(", password=<sensitive>")

View file

@ -15,6 +15,8 @@ const (
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// FieldLastPush holds the string denoting the last_push field in the database.
FieldLastPush = "last_push"
// FieldMachineId holds the string denoting the machineid field in the database.
FieldMachineId = "machine_id"
// FieldPassword holds the string denoting the password field in the database.
@ -47,6 +49,7 @@ var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldLastPush,
FieldMachineId,
FieldPassword,
FieldIpAddress,
@ -71,6 +74,8 @@ var (
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// DefaultLastPush holds the default value on creation for the "last_push" field.
DefaultLastPush func() time.Time
// ScenariosValidator is a validator for the "scenarios" field. It is called by the builders before save.
ScenariosValidator func(string) error
// DefaultIsValidated holds the default value on creation for the "isValidated" field.

View file

@ -107,6 +107,13 @@ func UpdatedAt(v time.Time) predicate.Machine {
})
}
// LastPush applies equality check predicate on the "last_push" field. It's identical to LastPushEQ.
func LastPush(v time.Time) predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldLastPush), v))
})
}
// MachineId applies equality check predicate on the "machineId" field. It's identical to MachineIdEQ.
func MachineId(v string) predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
@ -308,6 +315,96 @@ func UpdatedAtLTE(v time.Time) predicate.Machine {
})
}
// LastPushEQ applies the EQ predicate on the "last_push" field.
func LastPushEQ(v time.Time) predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
s.Where(sql.EQ(s.C(FieldLastPush), v))
})
}
// LastPushNEQ applies the NEQ predicate on the "last_push" field.
func LastPushNEQ(v time.Time) predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
s.Where(sql.NEQ(s.C(FieldLastPush), v))
})
}
// LastPushIn applies the In predicate on the "last_push" field.
func LastPushIn(vs ...time.Time) predicate.Machine {
v := make([]interface{}, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Machine(func(s *sql.Selector) {
// if not arguments were provided, append the FALSE constants,
// since we can't apply "IN ()". This will make this predicate falsy.
if len(v) == 0 {
s.Where(sql.False())
return
}
s.Where(sql.In(s.C(FieldLastPush), v...))
})
}
// LastPushNotIn applies the NotIn predicate on the "last_push" field.
func LastPushNotIn(vs ...time.Time) predicate.Machine {
v := make([]interface{}, len(vs))
for i := range v {
v[i] = vs[i]
}
return predicate.Machine(func(s *sql.Selector) {
// if not arguments were provided, append the FALSE constants,
// since we can't apply "IN ()". This will make this predicate falsy.
if len(v) == 0 {
s.Where(sql.False())
return
}
s.Where(sql.NotIn(s.C(FieldLastPush), v...))
})
}
// LastPushGT applies the GT predicate on the "last_push" field.
func LastPushGT(v time.Time) predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
s.Where(sql.GT(s.C(FieldLastPush), v))
})
}
// LastPushGTE applies the GTE predicate on the "last_push" field.
func LastPushGTE(v time.Time) predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
s.Where(sql.GTE(s.C(FieldLastPush), v))
})
}
// LastPushLT applies the LT predicate on the "last_push" field.
func LastPushLT(v time.Time) predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
s.Where(sql.LT(s.C(FieldLastPush), v))
})
}
// LastPushLTE applies the LTE predicate on the "last_push" field.
func LastPushLTE(v time.Time) predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
s.Where(sql.LTE(s.C(FieldLastPush), v))
})
}
// LastPushIsNil applies the IsNil predicate on the "last_push" field.
func LastPushIsNil() predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
s.Where(sql.IsNull(s.C(FieldLastPush)))
})
}
// LastPushNotNil applies the NotNil predicate on the "last_push" field.
func LastPushNotNil() predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {
s.Where(sql.NotNull(s.C(FieldLastPush)))
})
}
// MachineIdEQ applies the EQ predicate on the "machineId" field.
func MachineIdEQ(v string) predicate.Machine {
return predicate.Machine(func(s *sql.Selector) {

View file

@ -49,6 +49,20 @@ func (mc *MachineCreate) SetNillableUpdatedAt(t *time.Time) *MachineCreate {
return mc
}
// SetLastPush sets the "last_push" field.
func (mc *MachineCreate) SetLastPush(t time.Time) *MachineCreate {
mc.mutation.SetLastPush(t)
return mc
}
// SetNillableLastPush sets the "last_push" field if the given value is not nil.
func (mc *MachineCreate) SetNillableLastPush(t *time.Time) *MachineCreate {
if t != nil {
mc.SetLastPush(*t)
}
return mc
}
// SetMachineId sets the "machineId" field.
func (mc *MachineCreate) SetMachineId(s string) *MachineCreate {
mc.mutation.SetMachineId(s)
@ -217,6 +231,10 @@ func (mc *MachineCreate) defaults() {
v := machine.DefaultUpdatedAt()
mc.mutation.SetUpdatedAt(v)
}
if _, ok := mc.mutation.LastPush(); !ok {
v := machine.DefaultLastPush()
mc.mutation.SetLastPush(v)
}
if _, ok := mc.mutation.IsValidated(); !ok {
v := machine.DefaultIsValidated
mc.mutation.SetIsValidated(v)
@ -291,6 +309,14 @@ func (mc *MachineCreate) createSpec() (*Machine, *sqlgraph.CreateSpec) {
})
_node.UpdatedAt = value
}
if value, ok := mc.mutation.LastPush(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: machine.FieldLastPush,
})
_node.LastPush = value
}
if value, ok := mc.mutation.MachineId(); ok {
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
Type: field.TypeString,

View file

@ -56,6 +56,26 @@ func (mu *MachineUpdate) SetNillableUpdatedAt(t *time.Time) *MachineUpdate {
return mu
}
// SetLastPush sets the "last_push" field.
func (mu *MachineUpdate) SetLastPush(t time.Time) *MachineUpdate {
mu.mutation.SetLastPush(t)
return mu
}
// SetNillableLastPush sets the "last_push" field if the given value is not nil.
func (mu *MachineUpdate) SetNillableLastPush(t *time.Time) *MachineUpdate {
if t != nil {
mu.SetLastPush(*t)
}
return mu
}
// ClearLastPush clears the value of the "last_push" field.
func (mu *MachineUpdate) ClearLastPush() *MachineUpdate {
mu.mutation.ClearLastPush()
return mu
}
// SetMachineId sets the "machineId" field.
func (mu *MachineUpdate) SetMachineId(s string) *MachineUpdate {
mu.mutation.SetMachineId(s)
@ -291,6 +311,19 @@ func (mu *MachineUpdate) sqlSave(ctx context.Context) (n int, err error) {
Column: machine.FieldUpdatedAt,
})
}
if value, ok := mu.mutation.LastPush(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: machine.FieldLastPush,
})
}
if mu.mutation.LastPushCleared() {
_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Column: machine.FieldLastPush,
})
}
if value, ok := mu.mutation.MachineId(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString,
@ -459,6 +492,26 @@ func (muo *MachineUpdateOne) SetNillableUpdatedAt(t *time.Time) *MachineUpdateOn
return muo
}
// SetLastPush sets the "last_push" field.
func (muo *MachineUpdateOne) SetLastPush(t time.Time) *MachineUpdateOne {
muo.mutation.SetLastPush(t)
return muo
}
// SetNillableLastPush sets the "last_push" field if the given value is not nil.
func (muo *MachineUpdateOne) SetNillableLastPush(t *time.Time) *MachineUpdateOne {
if t != nil {
muo.SetLastPush(*t)
}
return muo
}
// ClearLastPush clears the value of the "last_push" field.
func (muo *MachineUpdateOne) ClearLastPush() *MachineUpdateOne {
muo.mutation.ClearLastPush()
return muo
}
// SetMachineId sets the "machineId" field.
func (muo *MachineUpdateOne) SetMachineId(s string) *MachineUpdateOne {
muo.mutation.SetMachineId(s)
@ -718,6 +771,19 @@ func (muo *MachineUpdateOne) sqlSave(ctx context.Context) (_node *Machine, err e
Column: machine.FieldUpdatedAt,
})
}
if value, ok := muo.mutation.LastPush(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Value: value,
Column: machine.FieldLastPush,
})
}
if muo.mutation.LastPushCleared() {
_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
Type: field.TypeTime,
Column: machine.FieldLastPush,
})
}
if value, ok := muo.mutation.MachineId(); ok {
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
Type: field.TypeString,

View file

@ -137,6 +137,7 @@ var (
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "created_at", Type: field.TypeTime},
{Name: "updated_at", Type: field.TypeTime},
{Name: "last_push", Type: field.TypeTime, Nullable: true},
{Name: "machine_id", Type: field.TypeString, Unique: true},
{Name: "password", Type: field.TypeString},
{Name: "ip_address", Type: field.TypeString},

View file

@ -4986,6 +4986,7 @@ type MachineMutation struct {
id *int
created_at *time.Time
updated_at *time.Time
last_push *time.Time
machineId *string
password *string
ipAddress *string
@ -5153,6 +5154,55 @@ func (m *MachineMutation) ResetUpdatedAt() {
m.updated_at = nil
}
// SetLastPush sets the "last_push" field.
func (m *MachineMutation) SetLastPush(t time.Time) {
m.last_push = &t
}
// LastPush returns the value of the "last_push" field in the mutation.
func (m *MachineMutation) LastPush() (r time.Time, exists bool) {
v := m.last_push
if v == nil {
return
}
return *v, true
}
// OldLastPush returns the old "last_push" field's value of the Machine entity.
// If the Machine object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *MachineMutation) OldLastPush(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, fmt.Errorf("OldLastPush is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, fmt.Errorf("OldLastPush requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldLastPush: %w", err)
}
return oldValue.LastPush, nil
}
// ClearLastPush clears the value of the "last_push" field.
func (m *MachineMutation) ClearLastPush() {
m.last_push = nil
m.clearedFields[machine.FieldLastPush] = struct{}{}
}
// LastPushCleared returns if the "last_push" field was cleared in this mutation.
func (m *MachineMutation) LastPushCleared() bool {
_, ok := m.clearedFields[machine.FieldLastPush]
return ok
}
// ResetLastPush resets all changes to the "last_push" field.
func (m *MachineMutation) ResetLastPush() {
m.last_push = nil
delete(m.clearedFields, machine.FieldLastPush)
}
// SetMachineId sets the "machineId" field.
func (m *MachineMutation) SetMachineId(s string) {
m.machineId = &s
@ -5517,13 +5567,16 @@ func (m *MachineMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *MachineMutation) Fields() []string {
fields := make([]string, 0, 9)
fields := make([]string, 0, 10)
if m.created_at != nil {
fields = append(fields, machine.FieldCreatedAt)
}
if m.updated_at != nil {
fields = append(fields, machine.FieldUpdatedAt)
}
if m.last_push != nil {
fields = append(fields, machine.FieldLastPush)
}
if m.machineId != nil {
fields = append(fields, machine.FieldMachineId)
}
@ -5557,6 +5610,8 @@ func (m *MachineMutation) Field(name string) (ent.Value, bool) {
return m.CreatedAt()
case machine.FieldUpdatedAt:
return m.UpdatedAt()
case machine.FieldLastPush:
return m.LastPush()
case machine.FieldMachineId:
return m.MachineId()
case machine.FieldPassword:
@ -5584,6 +5639,8 @@ func (m *MachineMutation) OldField(ctx context.Context, name string) (ent.Value,
return m.OldCreatedAt(ctx)
case machine.FieldUpdatedAt:
return m.OldUpdatedAt(ctx)
case machine.FieldLastPush:
return m.OldLastPush(ctx)
case machine.FieldMachineId:
return m.OldMachineId(ctx)
case machine.FieldPassword:
@ -5621,6 +5678,13 @@ func (m *MachineMutation) SetField(name string, value ent.Value) error {
}
m.SetUpdatedAt(v)
return nil
case machine.FieldLastPush:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetLastPush(v)
return nil
case machine.FieldMachineId:
v, ok := value.(string)
if !ok {
@ -5700,6 +5764,9 @@ func (m *MachineMutation) AddField(name string, value ent.Value) error {
// mutation.
func (m *MachineMutation) ClearedFields() []string {
var fields []string
if m.FieldCleared(machine.FieldLastPush) {
fields = append(fields, machine.FieldLastPush)
}
if m.FieldCleared(machine.FieldScenarios) {
fields = append(fields, machine.FieldScenarios)
}
@ -5723,6 +5790,9 @@ func (m *MachineMutation) FieldCleared(name string) bool {
// error if the field is not defined in the schema.
func (m *MachineMutation) ClearField(name string) error {
switch name {
case machine.FieldLastPush:
m.ClearLastPush()
return nil
case machine.FieldScenarios:
m.ClearScenarios()
return nil
@ -5746,6 +5816,9 @@ func (m *MachineMutation) ResetField(name string) error {
case machine.FieldUpdatedAt:
m.ResetUpdatedAt()
return nil
case machine.FieldLastPush:
m.ResetLastPush()
return nil
case machine.FieldMachineId:
m.ResetMachineId()
return nil

View file

@ -112,12 +112,16 @@ func init() {
machineDescUpdatedAt := machineFields[1].Descriptor()
// machine.DefaultUpdatedAt holds the default value on creation for the updated_at field.
machine.DefaultUpdatedAt = machineDescUpdatedAt.Default.(func() time.Time)
// machineDescLastPush is the schema descriptor for last_push field.
machineDescLastPush := machineFields[2].Descriptor()
// machine.DefaultLastPush holds the default value on creation for the last_push field.
machine.DefaultLastPush = machineDescLastPush.Default.(func() time.Time)
// machineDescScenarios is the schema descriptor for scenarios field.
machineDescScenarios := machineFields[5].Descriptor()
machineDescScenarios := machineFields[6].Descriptor()
// machine.ScenariosValidator is a validator for the "scenarios" field. It is called by the builders before save.
machine.ScenariosValidator = machineDescScenarios.Validators[0].(func(string) error)
// machineDescIsValidated is the schema descriptor for isValidated field.
machineDescIsValidated := machineFields[7].Descriptor()
machineDescIsValidated := machineFields[8].Descriptor()
// machine.DefaultIsValidated holds the default value on creation for the isValidated field.
machine.DefaultIsValidated = machineDescIsValidated.Default.(bool)
metaFields := schema.Meta{}.Fields()

View file

@ -20,6 +20,8 @@ func (Machine) Fields() []ent.Field {
Default(time.Now),
field.Time("updated_at").
Default(time.Now),
field.Time("last_push").
Default(time.Now).Optional(),
field.String("machineId").Unique(),
field.String("password").Sensitive(),
field.String("ipAddress"),

View file

@ -110,6 +110,14 @@ func (c *Client) DeleteWatcher(name string) error {
return nil
}
func (c *Client) UpdateMachineLastPush(machineID string) error {
_, err := c.Ent.Machine.Update().Where(machine.MachineIdEQ(machineID)).SetLastPush(time.Now()).Save(c.CTX)
if err != nil {
return errors.Wrapf(UpdateFail, "updating machine last_push: %s", err)
}
return nil
}
func (c *Client) UpdateMachineScenarios(scenarios string, ID int) error {
_, err := c.Ent.Machine.UpdateOneID(ID).
SetUpdatedAt(time.Now()).

View file

@ -37,6 +37,10 @@ type AddSignalsRequestItem struct {
// Required: true
ScenarioHash *string `json:"scenario_hash"`
// scenario trust
// Required: true
ScenarioTrust *string `json:"scenario_trust"`
// scenario version
// Required: true
ScenarioVersion *string `json:"scenario_version"`
@ -70,6 +74,10 @@ func (m *AddSignalsRequestItem) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateScenarioTrust(formats); err != nil {
res = append(res, err)
}
if err := m.validateScenarioVersion(formats); err != nil {
res = append(res, err)
}
@ -119,6 +127,15 @@ func (m *AddSignalsRequestItem) validateScenarioHash(formats strfmt.Registry) er
return nil
}
func (m *AddSignalsRequestItem) validateScenarioTrust(formats strfmt.Registry) error {
if err := validate.Required("scenario_trust", "body", m.ScenarioTrust); err != nil {
return err
}
return nil
}
func (m *AddSignalsRequestItem) validateScenarioVersion(formats strfmt.Registry) error {
if err := validate.Required("scenario_version", "body", m.ScenarioVersion); err != nil {

View file

@ -19,7 +19,7 @@ import (
// swagger:model Decision
type Decision struct {
// duration
// the duration of the decisions
// Required: true
Duration *string `json:"duration"`
@ -47,6 +47,9 @@ type Decision struct {
// Required: true
Type *string `json:"type"`
// the date until the decisions must be active
Until string `json:"until,omitempty"`
// the value of the decision scope : an IP, a range, a username, etc
// Required: true
Value *string `json:"value"`

View file

@ -777,17 +777,34 @@ definitions:
bouncers:
type: array
items:
$ref: '#/definitions/MetricsSoftInfo'
$ref: '#/definitions/MetricsBouncerInfo'
machines:
type: array
items:
$ref: '#/definitions/MetricsSoftInfo'
$ref: '#/definitions/MetricsAgentInfo'
required:
- apil_version
- bouncers
- machines
MetricsSoftInfo:
title: MetricsSoftInfo
MetricsBouncerInfo:
title: MetricsBouncerInfo
description: Software version info (so we can warn users about out-of-date software). The software name and the version are "guessed" from the user-agent
type: object
properties:
custom_name:
type: string
description: name of the component
name:
type: string
description: bouncer type (firewall, php ...)
version:
type: string
description: software version
last_pull:
type: string
description: last bouncer pull date
MetricsAgentInfo:
title: MetricsAgentInfo
description: Software version info (so we can warn users about out-of-date software). The software name and the version are "guessed" from the user-agent
type: object
properties:
@ -797,6 +814,12 @@ definitions:
version:
type: string
description: software version
last_update:
type: string
description: last agent update date
last_push:
type: string
description: last agent push date
Decision:
title: Decision
type: object
@ -818,7 +841,11 @@ definitions:
description: 'the value of the decision scope : an IP, a range, a username, etc'
type: string
duration:
description: 'the duration of the decisions'
type: string
until:
type: string
description: 'the date until the decisions must be active'
scenario:
type: string
simulated:
@ -926,6 +953,7 @@ definitions:
- "source"
- "start_at"
- "stop_at"
- "scenario_trust"
properties:
scenario_hash:
type: "string"
@ -939,6 +967,8 @@ definitions:
$ref: "#/definitions/Source"
scenario_version:
type: "string"
scenario_trust:
type: "string"
message:
type: "string"
description: "a human readable message"

View file

@ -26,11 +26,11 @@ type Metrics struct {
// bouncers
// Required: true
Bouncers []*MetricsSoftInfo `json:"bouncers"`
Bouncers []*MetricsBouncerInfo `json:"bouncers"`
// machines
// Required: true
Machines []*MetricsSoftInfo `json:"machines"`
Machines []*MetricsAgentInfo `json:"machines"`
}
// Validate validates this metrics

View file

@ -12,12 +12,18 @@ import (
"github.com/go-openapi/swag"
)
// MetricsSoftInfo MetricsSoftInfo
// MetricsAgentInfo MetricsAgentInfo
//
// Software version info (so we can warn users about out-of-date software). The software name and the version are "guessed" from the user-agent
//
// swagger:model MetricsSoftInfo
type MetricsSoftInfo struct {
// swagger:model MetricsAgentInfo
type MetricsAgentInfo struct {
// last agent push date
LastPush string `json:"last_push,omitempty"`
// last agent update date
LastUpdate string `json:"last_update,omitempty"`
// name of the component
Name string `json:"name,omitempty"`
@ -26,18 +32,18 @@ type MetricsSoftInfo struct {
Version string `json:"version,omitempty"`
}
// Validate validates this metrics soft info
func (m *MetricsSoftInfo) Validate(formats strfmt.Registry) error {
// Validate validates this metrics agent info
func (m *MetricsAgentInfo) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this metrics soft info based on context it is used
func (m *MetricsSoftInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
// ContextValidate validates this metrics agent info based on context it is used
func (m *MetricsAgentInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *MetricsSoftInfo) MarshalBinary() ([]byte, error) {
func (m *MetricsAgentInfo) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
@ -45,8 +51,8 @@ func (m *MetricsSoftInfo) MarshalBinary() ([]byte, error) {
}
// UnmarshalBinary interface implementation
func (m *MetricsSoftInfo) UnmarshalBinary(b []byte) error {
var res MetricsSoftInfo
func (m *MetricsAgentInfo) UnmarshalBinary(b []byte) error {
var res MetricsAgentInfo
if err := swag.ReadJSON(b, &res); err != nil {
return err
}

View file

@ -0,0 +1,61 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// MetricsBouncerInfo MetricsBouncerInfo
//
// Software version info (so we can warn users about out-of-date software). The software name and the version are "guessed" from the user-agent
//
// swagger:model MetricsBouncerInfo
type MetricsBouncerInfo struct {
// name of the component
CustomName string `json:"custom_name,omitempty"`
// last bouncer pull date
LastPull string `json:"last_pull,omitempty"`
// bouncer type (firewall, php ...)
Name string `json:"name,omitempty"`
// software version
Version string `json:"version,omitempty"`
}
// Validate validates this metrics bouncer info
func (m *MetricsBouncerInfo) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this metrics bouncer info based on context it is used
func (m *MetricsBouncerInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *MetricsBouncerInfo) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *MetricsBouncerInfo) UnmarshalBinary(b []byte) error {
var res MetricsBouncerInfo
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -216,3 +216,12 @@ func Int32Ptr(i int32) *int32 {
func BoolPtr(b bool) *bool {
return &b
}
func InSlice(str string, slice []string) bool {
for _, item := range slice {
if str == item {
return true
}
}
return false
}

View file

@ -60,6 +60,7 @@ install -m 644 -D config/patterns/* -t %{buildroot}%{_sysconfdir}/crowdsec/patte
install -m 644 -D config/config.yaml %{buildroot}%{_sysconfdir}/crowdsec
install -m 644 -D config/simulation.yaml %{buildroot}%{_sysconfdir}/crowdsec
install -m 644 -D config/profiles.yaml %{buildroot}%{_sysconfdir}/crowdsec
install -m 644 -D config/console_config.yaml %{buildroot}%{_sysconfdir}/crowdsec
install -m 644 -D %{SOURCE1} %{buildroot}%{_presetdir}
install -m 551 plugins/notifications/slack/notification-slack %{buildroot}%{_libdir}/%{name}/plugins/

View file

@ -31,6 +31,8 @@ CSCLI_BIN="./cmd/crowdsec-cli/cscli"
CLIENT_SECRETS="local_api_credentials.yaml"
LAPI_SECRETS="online_api_credentials.yaml"
CONSOLE_FILE="console_config.yaml"
BIN_INSTALL_PATH="/usr/local/bin"
CROWDSEC_BIN_INSTALLED="${BIN_INSTALL_PATH}/crowdsec"
@ -406,6 +408,7 @@ install_crowdsec() {
install -v -m 644 -D ./config/acquis.yaml "${CROWDSEC_CONFIG_PATH}" 1> /dev/null || exit
install -v -m 644 -D ./config/profiles.yaml "${CROWDSEC_CONFIG_PATH}" 1> /dev/null || exit
install -v -m 644 -D ./config/simulation.yaml "${CROWDSEC_CONFIG_PATH}" 1> /dev/null || exit
install -v -m 644 -D ./config/"${CONSOLE_FILE}" "${CROWDSEC_CONFIG_PATH}" 1> /dev/null || exit
mkdir -p ${PID_DIR} || exit
PID=${PID_DIR} DATA=${CROWDSEC_DATA_DIR} CFG=${CROWDSEC_CONFIG_PATH} envsubst '$CFG $PID $DATA' < ./config/user.yaml > ${CROWDSEC_CONFIG_PATH}"/user.yaml" || log_fatal "unable to generate user configuration file"