Merge branch 'master' into postoverflow_reinject_meta

This commit is contained in:
Manuel Sabban 2023-12-05 08:49:02 +01:00 committed by GitHub
commit f7f484249f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 237 additions and 208 deletions

View file

@ -110,7 +110,7 @@ func NewCapiRegisterCmd() *cobra.Command {
if err != nil { if err != nil {
return fmt.Errorf("write api credentials in '%s' failed: %w", dumpFile, err) return fmt.Errorf("write api credentials in '%s' failed: %w", dumpFile, err)
} }
log.Printf("Central API credentials dumped to '%s'", dumpFile) log.Printf("Central API credentials written to '%s'", dumpFile)
} else { } else {
fmt.Printf("%s\n", string(apiConfigDump)) fmt.Printf("%s\n", string(apiConfigDump))
} }

View file

@ -183,7 +183,7 @@ func restoreConfigFromDirectory(dirPath string, oldBackup bool) error {
if csConfig.API.Server.OnlineClient != nil && csConfig.API.Server.OnlineClient.CredentialsFilePath != "" { if csConfig.API.Server.OnlineClient != nil && csConfig.API.Server.OnlineClient.CredentialsFilePath != "" {
apiConfigDumpFile = csConfig.API.Server.OnlineClient.CredentialsFilePath apiConfigDumpFile = csConfig.API.Server.OnlineClient.CredentialsFilePath
} }
err = os.WriteFile(apiConfigDumpFile, apiConfigDump, 0o644) err = os.WriteFile(apiConfigDumpFile, apiConfigDump, 0o600)
if err != nil { if err != nil {
return fmt.Errorf("write api credentials in '%s' failed: %s", apiConfigDumpFile, err) return fmt.Errorf("write api credentials in '%s' failed: %s", apiConfigDumpFile, err)
} }

View file

@ -232,7 +232,7 @@ func NewDecisionsImportCmd() *cobra.Command {
Short: "Import decisions from a file or pipe", Short: "Import decisions from a file or pipe",
Long: "expected format:\n" + Long: "expected format:\n" +
"csv : any of duration,reason,scope,type,value, with a header line\n" + "csv : any of duration,reason,scope,type,value, with a header line\n" +
`json : {"duration" : "24h", "reason" : "my_scenario", "scope" : "ip", "type" : "ban", "value" : "x.y.z.z"}`, "json :" + "`{" + `"duration" : "24h", "reason" : "my_scenario", "scope" : "ip", "type" : "ban", "value" : "x.y.z.z"` + "}`",
DisableAutoGenTag: true, DisableAutoGenTag: true,
Example: `decisions.csv: Example: `decisions.csv:
duration,scope,value duration,scope,value

View file

@ -21,10 +21,16 @@ func GetLineCountForFile(filepath string) (int, error) {
} }
defer f.Close() defer f.Close()
lc := 0 lc := 0
fs := bufio.NewScanner(f) fs := bufio.NewReader(f)
for fs.Scan() { for {
input, err := fs.ReadBytes('\n')
if len(input) > 1 {
lc++ lc++
} }
if err != nil && err == io.EOF {
break
}
}
return lc, nil return lc, nil
} }
@ -79,19 +85,6 @@ func runExplain(cmd *cobra.Command, args []string) error {
return err return err
} }
fileInfo, _ := os.Stdin.Stat()
if logType == "" || (logLine == "" && logFile == "" && dsn == "") {
printHelp(cmd)
fmt.Println()
fmt.Printf("Please provide --type flag\n")
os.Exit(1)
}
if logFile == "-" && ((fileInfo.Mode() & os.ModeCharDevice) == os.ModeCharDevice) {
return fmt.Errorf("the option -f - is intended to work with pipes")
}
var f *os.File var f *os.File
// using empty string fallback to /tmp // using empty string fallback to /tmp
@ -99,6 +92,13 @@ func runExplain(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return fmt.Errorf("couldn't create a temporary directory to store cscli explain result: %s", err) return fmt.Errorf("couldn't create a temporary directory to store cscli explain result: %s", err)
} }
defer func() {
if _, err := os.Stat(dir); !os.IsNotExist(err) {
if err := os.RemoveAll(dir); err != nil {
log.Errorf("unable to delete temporary directory '%s': %s", dir, err)
}
}
}()
tmpFile := "" tmpFile := ""
// we create a temporary log file if a log line/stdin has been provided // we create a temporary log file if a log line/stdin has been provided
if logLine != "" || logFile == "-" { if logLine != "" || logFile == "-" {
@ -121,13 +121,15 @@ func runExplain(cmd *cobra.Command, args []string) error {
if err != nil && err == io.EOF { if err != nil && err == io.EOF {
break break
} }
if len(input) > 1 {
_, err = f.Write(input) _, err = f.Write(input)
if err != nil { }
if err != nil || len(input) <= 1 {
errCount++ errCount++
} }
} }
if errCount > 0 { if errCount > 0 {
log.Warnf("Failed to write %d lines to tmp file", errCount) log.Warnf("Failed to write %d lines to %s", errCount, tmpFile)
} }
} }
f.Close() f.Close()
@ -145,8 +147,12 @@ func runExplain(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
log.Debugf("file %s has %d lines", absolutePath, lineCount)
if lineCount == 0 {
return fmt.Errorf("the log file is empty: %s", absolutePath)
}
if lineCount > 100 { if lineCount > 100 {
log.Warnf("The log file contains %d lines. This may take a lot of resources.", lineCount) log.Warnf("%s contains %d lines. This may take a lot of resources.", absolutePath, lineCount)
} }
} }
@ -166,12 +172,6 @@ func runExplain(cmd *cobra.Command, args []string) error {
return fmt.Errorf("fail to run crowdsec for test: %v", err) return fmt.Errorf("fail to run crowdsec for test: %v", err)
} }
// rm the temporary log file if only a log line/stdin was provided
if tmpFile != "" {
if err := os.Remove(tmpFile); err != nil {
return fmt.Errorf("unable to remove tmp log file '%s': %+v", tmpFile, err)
}
}
parserDumpFile := filepath.Join(dir, hubtest.ParserResultFileName) parserDumpFile := filepath.Join(dir, hubtest.ParserResultFileName)
bucketStateDumpFile := filepath.Join(dir, hubtest.BucketPourResultFileName) bucketStateDumpFile := filepath.Join(dir, hubtest.BucketPourResultFileName)
@ -187,10 +187,6 @@ func runExplain(cmd *cobra.Command, args []string) error {
hubtest.DumpTree(*parserDump, *bucketStateDump, opts) hubtest.DumpTree(*parserDump, *bucketStateDump, opts)
if err := os.RemoveAll(dir); err != nil {
return fmt.Errorf("unable to delete temporary directory '%s': %s", dir, err)
}
return nil return nil
} }
@ -210,6 +206,45 @@ tail -n 5 myfile.log | cscli explain --type nginx -f -
Args: cobra.ExactArgs(0), Args: cobra.ExactArgs(0),
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: runExplain, RunE: runExplain,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
flags := cmd.Flags()
logFile, err := flags.GetString("file")
if err != nil {
return err
}
dsn, err := flags.GetString("dsn")
if err != nil {
return err
}
logLine, err := flags.GetString("log")
if err != nil {
return err
}
logType, err := flags.GetString("type")
if err != nil {
return err
}
if logLine == "" && logFile == "" && dsn == "" {
printHelp(cmd)
fmt.Println()
return fmt.Errorf("please provide --log, --file or --dsn flag")
}
if logType == "" {
printHelp(cmd)
fmt.Println()
return fmt.Errorf("please provide --type flag")
}
fileInfo, _ := os.Stdin.Stat()
if logFile == "-" && ((fileInfo.Mode() & os.ModeCharDevice) == os.ModeCharDevice) {
return fmt.Errorf("the option -f - is intended to work with pipes")
}
return nil
},
} }
flags := cmdExplain.Flags() flags := cmdExplain.Flags()

View file

@ -150,11 +150,11 @@ func runLapiRegister(cmd *cobra.Command, args []string) error {
log.Fatalf("unable to marshal api credentials: %s", err) log.Fatalf("unable to marshal api credentials: %s", err)
} }
if dumpFile != "" { if dumpFile != "" {
err = os.WriteFile(dumpFile, apiConfigDump, 0644) err = os.WriteFile(dumpFile, apiConfigDump, 0o600)
if err != nil { if err != nil {
log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err) log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err)
} }
log.Printf("Local API credentials dumped to '%s'", dumpFile) log.Printf("Local API credentials written to '%s'", dumpFile)
} else { } else {
fmt.Printf("%s\n", string(apiConfigDump)) fmt.Printf("%s\n", string(apiConfigDump))
} }

View file

@ -30,9 +30,7 @@ import (
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
) )
var ( const passwordLength = 64
passwordLength = 64
)
func generatePassword(length int) string { func generatePassword(length int) string {
upper := "ABCDEFGHIJKLMNOPQRSTUVWXY" upper := "ABCDEFGHIJKLMNOPQRSTUVWXY"
@ -43,6 +41,7 @@ func generatePassword(length int) string {
charsetLength := len(charset) charsetLength := len(charset)
buf := make([]byte, length) buf := make([]byte, length)
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
rInt, err := saferand.Int(saferand.Reader, big.NewInt(int64(charsetLength))) rInt, err := saferand.Int(saferand.Reader, big.NewInt(int64(charsetLength)))
if err != nil { if err != nil {
@ -190,7 +189,6 @@ cscli machines add MyTestMachine --password MyPassword
} }
func runMachinesAdd(cmd *cobra.Command, args []string) error { func runMachinesAdd(cmd *cobra.Command, args []string) error {
var dumpFile string
var err error var err error
flags := cmd.Flags() flags := cmd.Flags()
@ -200,7 +198,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
return err return err
} }
outputFile, err := flags.GetString("file") dumpFile, err := flags.GetString("file")
if err != nil { if err != nil {
return err return err
} }
@ -220,7 +218,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
return err return err
} }
forceAdd, err := flags.GetBool("force") force, err := flags.GetBool("force")
if err != nil { if err != nil {
return err return err
} }
@ -242,17 +240,28 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
} }
/*check if file already exists*/ /*check if file already exists*/
if outputFile != "" { if dumpFile == "" && csConfig.API.Client != nil && csConfig.API.Client.CredentialsFilePath != "" {
dumpFile = outputFile credFile := csConfig.API.Client.CredentialsFilePath
} else if csConfig.API.Client != nil && csConfig.API.Client.CredentialsFilePath != "" { // use the default only if the file does not exist
_, err := os.Stat(credFile)
switch {
case os.IsNotExist(err) || force:
dumpFile = csConfig.API.Client.CredentialsFilePath dumpFile = csConfig.API.Client.CredentialsFilePath
case err != nil:
return fmt.Errorf("unable to stat '%s': %s", credFile, err)
default:
return fmt.Errorf(`credentials file '%s' already exists: please remove it, use "--force" or specify a different file with "-f" ("-f -" for standard output)`, credFile)
}
}
if dumpFile == "" {
return fmt.Errorf(`please specify a file to dump credentials to, with -f ("-f -" for standard output)`)
} }
// create a password if it's not specified by user // create a password if it's not specified by user
if machinePassword == "" && !interactive { if machinePassword == "" && !interactive {
if !autoAdd { if !autoAdd {
printHelp(cmd) return fmt.Errorf("please specify a password with --password or use --auto")
return nil
} }
machinePassword = generatePassword(passwordLength) machinePassword = generatePassword(passwordLength)
} else if machinePassword == "" && interactive { } else if machinePassword == "" && interactive {
@ -262,7 +271,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
survey.AskOne(qs, &machinePassword) survey.AskOne(qs, &machinePassword)
} }
password := strfmt.Password(machinePassword) password := strfmt.Password(machinePassword)
_, err = dbClient.CreateMachine(&machineID, &password, "", true, forceAdd, types.PasswordAuthType) _, err = dbClient.CreateMachine(&machineID, &password, "", true, force, types.PasswordAuthType)
if err != nil { if err != nil {
return fmt.Errorf("unable to create machine: %s", err) return fmt.Errorf("unable to create machine: %s", err)
} }
@ -291,7 +300,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return fmt.Errorf("write api credentials in '%s' failed: %s", dumpFile, err) return fmt.Errorf("write api credentials in '%s' failed: %s", dumpFile, err)
} }
log.Printf("API credentials dumped to '%s'", dumpFile) log.Printf("API credentials written to '%s'", dumpFile)
} else { } else {
fmt.Printf("%s\n", string(apiConfigDump)) fmt.Printf("%s\n", string(apiConfigDump))
} }

View file

@ -58,13 +58,12 @@ func (s *HTTPPlugin) Notify(ctx context.Context, notification *protobufs.Notific
if err != nil { if err != nil {
return nil, err return nil, err
} }
for headerName, headerValue := range cfg.Headers { for headerName, headerValue := range cfg.Headers {
logger.Debug(fmt.Sprintf("adding header %s: %s", headerName, headerValue)) logger.Debug(fmt.Sprintf("adding header %s: %s", headerName, headerValue))
request.Header.Add(headerName, headerValue) request.Header.Add(headerName, headerValue)
} }
logger.Debug(fmt.Sprintf("making HTTP %s call to %s with body %s", cfg.Method, cfg.URL, notification.Text)) logger.Debug(fmt.Sprintf("making HTTP %s call to %s with body %s", cfg.Method, cfg.URL, notification.Text))
resp, err := client.Do(request) resp, err := client.Do(request.WithContext(ctx))
if err != nil { if err != nil {
logger.Error(fmt.Sprintf("Failed to make HTTP request : %s", err)) logger.Error(fmt.Sprintf("Failed to make HTTP request : %s", err))
return nil, err return nil, err

View file

@ -90,7 +90,7 @@ func (s *SentinelPlugin) Notify(ctx context.Context, notification *protobufs.Not
req.Header.Set("x-ms-date", now) req.Header.Set("x-ms-date", now)
client := &http.Client{} client := &http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req.WithContext(ctx))
if err != nil { if err != nil {
logger.Error("failed to send request", "error", err) logger.Error("failed to send request", "error", err)
return &protobufs.Empty{}, err return &protobufs.Empty{}, err

View file

@ -38,10 +38,9 @@ func (n *Notify) Notify(ctx context.Context, notification *protobufs.Notificatio
if cfg.LogLevel != nil && *cfg.LogLevel != "" { if cfg.LogLevel != nil && *cfg.LogLevel != "" {
logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel)) logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel))
} }
logger.Info(fmt.Sprintf("found notify signal for %s config", notification.Name)) logger.Info(fmt.Sprintf("found notify signal for %s config", notification.Name))
logger.Debug(fmt.Sprintf("posting to %s webhook, message %s", cfg.Webhook, notification.Text)) logger.Debug(fmt.Sprintf("posting to %s webhook, message %s", cfg.Webhook, notification.Text))
err := slack.PostWebhook(n.ConfigByName[notification.Name].Webhook, &slack.WebhookMessage{ err := slack.PostWebhookContext(ctx, n.ConfigByName[notification.Name].Webhook, &slack.WebhookMessage{
Text: notification.Text, Text: notification.Text,
}) })
if err != nil { if err != nil {

View file

@ -65,7 +65,7 @@ func (s *Splunk) Notify(ctx context.Context, notification *protobufs.Notificatio
req.Header.Add("Authorization", fmt.Sprintf("Splunk %s", cfg.Token)) req.Header.Add("Authorization", fmt.Sprintf("Splunk %s", cfg.Token))
logger.Debug(fmt.Sprintf("posting event %s to %s", string(data), req.URL)) logger.Debug(fmt.Sprintf("posting event %s to %s", string(data), req.URL))
resp, err := s.Client.Do(req) resp, err := s.Client.Do(req.WithContext(ctx))
if err != nil { if err != nil {
return &protobufs.Empty{}, err return &protobufs.Empty{}, err
} }

View file

@ -106,13 +106,13 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
var flushScheduler *gocron.Scheduler var flushScheduler *gocron.Scheduler
dbClient, err := database.NewClient(config.DbConfig) dbClient, err := database.NewClient(config.DbConfig)
if err != nil { if err != nil {
return &APIServer{}, fmt.Errorf("unable to init database client: %w", err) return nil, fmt.Errorf("unable to init database client: %w", err)
} }
if config.DbConfig.Flush != nil { if config.DbConfig.Flush != nil {
flushScheduler, err = dbClient.StartFlushScheduler(config.DbConfig.Flush) flushScheduler, err = dbClient.StartFlushScheduler(config.DbConfig.Flush)
if err != nil { if err != nil {
return &APIServer{}, err return nil, err
} }
} }
@ -129,7 +129,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
if config.TrustedProxies != nil && config.UseForwardedForHeaders { if config.TrustedProxies != nil && config.UseForwardedForHeaders {
if err := router.SetTrustedProxies(*config.TrustedProxies); err != nil { if err := router.SetTrustedProxies(*config.TrustedProxies); err != nil {
return &APIServer{}, fmt.Errorf("while setting trusted_proxies: %w", err) return nil, fmt.Errorf("while setting trusted_proxies: %w", err)
} }
router.ForwardedByClientIP = true router.ForwardedByClientIP = true
} else { } else {
@ -215,7 +215,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
log.Printf("Loading CAPI manager") log.Printf("Loading CAPI manager")
apiClient, err = NewAPIC(config.OnlineClient, dbClient, config.ConsoleConfig, config.CapiWhitelists) apiClient, err = NewAPIC(config.OnlineClient, dbClient, config.ConsoleConfig, config.CapiWhitelists)
if err != nil { if err != nil {
return &APIServer{}, err return nil, err
} }
log.Infof("CAPI manager configured successfully") log.Infof("CAPI manager configured successfully")
isMachineEnrolled = isEnrolled(apiClient.apiClient) isMachineEnrolled = isEnrolled(apiClient.apiClient)
@ -225,7 +225,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
log.Infof("Machine is enrolled in the console, Loading PAPI Client") log.Infof("Machine is enrolled in the console, Loading PAPI Client")
papiClient, err = NewPAPI(apiClient, dbClient, config.ConsoleConfig, *config.PapiLogLevel) papiClient, err = NewPAPI(apiClient, dbClient, config.ConsoleConfig, *config.PapiLogLevel)
if err != nil { if err != nil {
return &APIServer{}, err return nil, err
} }
controller.DecisionDeleteChan = papiClient.Channels.DeleteDecisionChannel controller.DecisionDeleteChan = papiClient.Channels.DeleteDecisionChannel
} else { } else {
@ -241,7 +241,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
if trustedIPs, err := config.GetTrustedIPs(); err == nil { if trustedIPs, err := config.GetTrustedIPs(); err == nil {
controller.TrustedIPs = trustedIPs controller.TrustedIPs = trustedIPs
} else { } else {
return &APIServer{}, err return nil, err
} }
return &APIServer{ return &APIServer{

View file

@ -11,6 +11,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/crowdsecurity/go-cs-lib/cstest"
"github.com/crowdsecurity/go-cs-lib/version" "github.com/crowdsecurity/go-cs-lib/version"
middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1" middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
@ -295,8 +296,8 @@ func TestWithWrongDBConfig(t *testing.T) {
config.API.Server.DbConfig.Type = "test" config.API.Server.DbConfig.Type = "test"
apiServer, err := NewServer(config.API.Server) apiServer, err := NewServer(config.API.Server)
assert.Equal(t, apiServer, &APIServer{}) cstest.RequireErrorContains(t, err, "unable to init database client: unknown database type 'test'")
assert.Equal(t, "unable to init database client: unknown database type 'test'", err.Error()) assert.Nil(t, apiServer)
} }
func TestWithWrongFlushConfig(t *testing.T) { func TestWithWrongFlushConfig(t *testing.T) {
@ -305,8 +306,8 @@ func TestWithWrongFlushConfig(t *testing.T) {
config.API.Server.DbConfig.Flush.MaxItems = &maxItems config.API.Server.DbConfig.Flush.MaxItems = &maxItems
apiServer, err := NewServer(config.API.Server) apiServer, err := NewServer(config.API.Server)
assert.Equal(t, apiServer, &APIServer{}) cstest.RequireErrorContains(t, err, "max_items can't be zero or negative number")
assert.Equal(t, "max_items can't be zero or negative number", err.Error()) assert.Nil(t, apiServer)
} }
func TestUnknownPath(t *testing.T) { func TestUnknownPath(t *testing.T) {

View file

@ -55,132 +55,110 @@ func HashSHA512(str string) string {
return hashStr return hashStr
} }
func (a *APIKey) MiddlewareFunc() gin.HandlerFunc { func (a *APIKey) authTLS(c *gin.Context, logger *log.Entry) *ent.Bouncer {
return func(c *gin.Context) {
var bouncer *ent.Bouncer
var err error
if c.Request.TLS != nil && len(c.Request.TLS.PeerCertificates) > 0 {
if a.TlsAuth == nil { if a.TlsAuth == nil {
log.WithField("ip", c.ClientIP()).Error("TLS Auth is not configured but client presented a certificate") logger.Error("TLS Auth is not configured but client presented a certificate")
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"}) return nil
c.Abort()
return
} }
validCert, extractedCN, err := a.TlsAuth.ValidateCert(c) validCert, extractedCN, err := a.TlsAuth.ValidateCert(c)
if !validCert { if !validCert {
log.WithField("ip", c.ClientIP()).Errorf("invalid client certificate: %s", err) logger.Errorf("invalid client certificate: %s", err)
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"}) return nil
c.Abort()
return
} }
if err != nil { if err != nil {
log.WithField("ip", c.ClientIP()).Error(err) logger.Error(err)
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"}) return nil
c.Abort()
return
} }
logger = logger.WithFields(log.Fields{
"cn": extractedCN,
})
bouncerName := fmt.Sprintf("%s@%s", extractedCN, c.ClientIP()) bouncerName := fmt.Sprintf("%s@%s", extractedCN, c.ClientIP())
bouncer, err = a.DbClient.SelectBouncerByName(bouncerName) bouncer, err := a.DbClient.SelectBouncerByName(bouncerName)
//This is likely not the proper way, but isNotFound does not seem to work //This is likely not the proper way, but isNotFound does not seem to work
if err != nil && strings.Contains(err.Error(), "bouncer not found") { if err != nil && strings.Contains(err.Error(), "bouncer not found") {
//Because we have a valid cert, automatically create the bouncer in the database if it does not exist //Because we have a valid cert, automatically create the bouncer in the database if it does not exist
//Set a random API key, but it will never be used //Set a random API key, but it will never be used
apiKey, err := GenerateAPIKey(dummyAPIKeySize) apiKey, err := GenerateAPIKey(dummyAPIKeySize)
if err != nil { if err != nil {
log.WithFields(log.Fields{ logger.Errorf("error generating mock api key: %s", err)
"ip": c.ClientIP(), return nil
"cn": extractedCN,
}).Errorf("error generating mock api key: %s", err)
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort()
return
} }
log.WithFields(log.Fields{ logger.Infof("Creating bouncer %s", bouncerName)
"ip": c.ClientIP(),
"cn": extractedCN,
}).Infof("Creating bouncer %s", bouncerName)
bouncer, err = a.DbClient.CreateBouncer(bouncerName, c.ClientIP(), HashSHA512(apiKey), types.TlsAuthType) bouncer, err = a.DbClient.CreateBouncer(bouncerName, c.ClientIP(), HashSHA512(apiKey), types.TlsAuthType)
if err != nil { if err != nil {
log.WithFields(log.Fields{ logger.Errorf("while creating bouncer db entry: %s", err)
"ip": c.ClientIP(), return nil
"cn": extractedCN,
}).Errorf("creating bouncer db entry : %s", err)
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort()
return
} }
} else if err != nil { } else if err != nil {
//error while selecting bouncer //error while selecting bouncer
log.WithFields(log.Fields{ logger.Errorf("while selecting bouncers: %s", err)
"ip": c.ClientIP(), return nil
"cn": extractedCN,
}).Errorf("while selecting bouncers: %s", err)
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort()
return
} else if bouncer.AuthType != types.TlsAuthType { } else if bouncer.AuthType != types.TlsAuthType {
//bouncer was found in DB //bouncer was found in DB
log.WithFields(log.Fields{ logger.Errorf("bouncer isn't allowed to auth by TLS")
"ip": c.ClientIP(), return nil
"cn": extractedCN,
}).Errorf("bouncer isn't allowed to auth by TLS")
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort()
return
} }
} else { return bouncer
//API Key Authentication }
func (a *APIKey) authPlain(c *gin.Context, logger *log.Entry) *ent.Bouncer {
val, ok := c.Request.Header[APIKeyHeader] val, ok := c.Request.Header[APIKeyHeader]
if !ok { if !ok {
log.WithFields(log.Fields{ logger.Errorf("API key not found")
"ip": c.ClientIP(), return nil
}).Errorf("API key not found")
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort()
return
} }
hashStr := HashSHA512(val[0]) hashStr := HashSHA512(val[0])
bouncer, err = a.DbClient.SelectBouncer(hashStr)
bouncer, err := a.DbClient.SelectBouncer(hashStr)
if err != nil { if err != nil {
log.WithFields(log.Fields{ logger.Errorf("while fetching bouncer info: %s", err)
"ip": c.ClientIP(), return nil
}).Errorf("while fetching bouncer info: %s", err)
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort()
return
} }
if bouncer.AuthType != types.ApiKeyAuthType { if bouncer.AuthType != types.ApiKeyAuthType {
log.WithFields(log.Fields{ logger.Errorf("bouncer %s attempted to login using an API key but it is configured to auth with %s", bouncer.Name, bouncer.AuthType)
"ip": c.ClientIP(), return nil
}).Errorf("bouncer %s attempted to login using an API key but it is configured to auth with %s", bouncer.Name, bouncer.AuthType)
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort()
return
} }
return bouncer
}
func (a *APIKey) MiddlewareFunc() gin.HandlerFunc {
return func(c *gin.Context) {
var bouncer *ent.Bouncer
logger := log.WithFields(log.Fields{
"ip": c.ClientIP(),
})
if c.Request.TLS != nil && len(c.Request.TLS.PeerCertificates) > 0 {
bouncer = a.authTLS(c, logger)
} else {
bouncer = a.authPlain(c, logger)
} }
if bouncer == nil { if bouncer == nil {
log.WithFields(log.Fields{
"ip": c.ClientIP(),
}).Errorf("bouncer not found")
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"}) c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort() c.Abort()
return return
} }
//maybe we want to store the whole bouncer object in the context instead, this would avoid another db query logger = logger.WithFields(log.Fields{
//in StreamDecision "name": bouncer.Name,
})
// maybe we want to store the whole bouncer object in the context instead, this would avoid another db query
// in StreamDecision
c.Set("BOUNCER_NAME", bouncer.Name) c.Set("BOUNCER_NAME", bouncer.Name)
c.Set("BOUNCER_HASHED_KEY", bouncer.APIKey) c.Set("BOUNCER_HASHED_KEY", bouncer.APIKey)
if bouncer.IPAddress == "" { if bouncer.IPAddress == "" {
err = a.DbClient.UpdateBouncerIP(c.ClientIP(), bouncer.ID) if err := a.DbClient.UpdateBouncerIP(c.ClientIP(), bouncer.ID); err != nil {
if err != nil { logger.Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err)
log.WithFields(log.Fields{
"ip": c.ClientIP(),
"name": bouncer.Name,
}).Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err)
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"}) c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort() c.Abort()
return return
@ -189,12 +167,8 @@ func (a *APIKey) MiddlewareFunc() gin.HandlerFunc {
if bouncer.IPAddress != c.ClientIP() && bouncer.IPAddress != "" { if bouncer.IPAddress != c.ClientIP() && bouncer.IPAddress != "" {
log.Warningf("new IP address detected for bouncer '%s': %s (old: %s)", bouncer.Name, c.ClientIP(), bouncer.IPAddress) log.Warningf("new IP address detected for bouncer '%s': %s (old: %s)", bouncer.Name, c.ClientIP(), bouncer.IPAddress)
err = a.DbClient.UpdateBouncerIP(c.ClientIP(), bouncer.ID) if err := a.DbClient.UpdateBouncerIP(c.ClientIP(), bouncer.ID); err != nil {
if err != nil { logger.Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err)
log.WithFields(log.Fields{
"ip": c.ClientIP(),
"name": bouncer.Name,
}).Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err)
c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"}) c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
c.Abort() c.Abort()
return return
@ -202,21 +176,14 @@ func (a *APIKey) MiddlewareFunc() gin.HandlerFunc {
} }
useragent := strings.Split(c.Request.UserAgent(), "/") useragent := strings.Split(c.Request.UserAgent(), "/")
if len(useragent) != 2 { if len(useragent) != 2 {
log.WithFields(log.Fields{ logger.Warningf("bad user agent '%s'", c.Request.UserAgent())
"ip": c.ClientIP(),
"name": bouncer.Name,
}).Warningf("bad user agent '%s'", c.Request.UserAgent())
useragent = []string{c.Request.UserAgent(), "N/A"} useragent = []string{c.Request.UserAgent(), "N/A"}
} }
if bouncer.Version != useragent[1] || bouncer.Type != useragent[0] { if bouncer.Version != useragent[1] || bouncer.Type != useragent[0] {
if err := a.DbClient.UpdateBouncerTypeAndVersion(useragent[0], useragent[1], bouncer.ID); err != nil { if err := a.DbClient.UpdateBouncerTypeAndVersion(useragent[0], useragent[1], bouncer.ID); err != nil {
log.WithFields(log.Fields{ logger.Errorf("failed to update bouncer version and type: %s", err)
"ip": c.ClientIP(),
"name": bouncer.Name,
}).Errorf("failed to update bouncer version and type: %s", err)
c.JSON(http.StatusForbidden, gin.H{"message": "bad user agent"}) c.JSON(http.StatusForbidden, gin.H{"message": "bad user agent"})
c.Abort() c.Abort()
return return

View file

@ -14,7 +14,7 @@ func NewMiddlewares(dbClient *database.Client) (*Middlewares, error) {
ret.JWT, err = NewJWT(dbClient) ret.JWT, err = NewJWT(dbClient)
if err != nil { if err != nil {
return &Middlewares{}, err return nil, err
} }
ret.APIKey = NewAPIKey(dbClient) ret.APIKey = NewAPIKey(dbClient)

View file

@ -26,7 +26,7 @@ func (m *GRPCClient) Notify(ctx context.Context, notification *protobufs.Notific
done := make(chan error) done := make(chan error)
go func() { go func() {
_, err := m.client.Notify( _, err := m.client.Notify(
context.Background(), &protobufs.Notification{Text: notification.Text, Name: notification.Name}, ctx, &protobufs.Notification{Text: notification.Text, Name: notification.Name},
) )
done <- err done <- err
}() }()

View file

@ -13,7 +13,7 @@ import (
func (c *Client) SelectBouncer(apiKeyHash string) (*ent.Bouncer, error) { func (c *Client) SelectBouncer(apiKeyHash string) (*ent.Bouncer, error) {
result, err := c.Ent.Bouncer.Query().Where(bouncer.APIKeyEQ(apiKeyHash)).First(c.CTX) result, err := c.Ent.Bouncer.Query().Where(bouncer.APIKeyEQ(apiKeyHash)).First(c.CTX)
if err != nil { if err != nil {
return &ent.Bouncer{}, errors.Wrapf(QueryFail, "select bouncer: %s", err) return nil, err
} }
return result, nil return result, nil
@ -22,7 +22,7 @@ func (c *Client) SelectBouncer(apiKeyHash string) (*ent.Bouncer, error) {
func (c *Client) SelectBouncerByName(bouncerName string) (*ent.Bouncer, error) { func (c *Client) SelectBouncerByName(bouncerName string) (*ent.Bouncer, error) {
result, err := c.Ent.Bouncer.Query().Where(bouncer.NameEQ(bouncerName)).First(c.CTX) result, err := c.Ent.Bouncer.Query().Where(bouncer.NameEQ(bouncerName)).First(c.CTX)
if err != nil { if err != nil {
return &ent.Bouncer{}, errors.Wrapf(QueryFail, "select bouncer: %s", err) return nil, err
} }
return result, nil return result, nil
@ -31,7 +31,7 @@ func (c *Client) SelectBouncerByName(bouncerName string) (*ent.Bouncer, error) {
func (c *Client) ListBouncers() ([]*ent.Bouncer, error) { func (c *Client) ListBouncers() ([]*ent.Bouncer, error) {
result, err := c.Ent.Bouncer.Query().All(c.CTX) result, err := c.Ent.Bouncer.Query().All(c.CTX)
if err != nil { if err != nil {
return []*ent.Bouncer{}, errors.Wrapf(QueryFail, "listing bouncer: %s", err) return nil, errors.Wrapf(QueryFail, "listing bouncers: %s", err)
} }
return result, nil return result, nil
} }

View file

@ -19,8 +19,8 @@ const CapiListsMachineID = types.ListOrigin
func (c *Client) CreateMachine(machineID *string, password *strfmt.Password, ipAddress string, isValidated bool, force bool, authType string) (*ent.Machine, error) { func (c *Client) CreateMachine(machineID *string, password *strfmt.Password, ipAddress string, isValidated bool, force bool, authType string) (*ent.Machine, error) {
hashPassword, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost) hashPassword, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost)
if err != nil { if err != nil {
c.Log.Warningf("CreateMachine : %s", err) c.Log.Warningf("CreateMachine: %s", err)
return nil, errors.Wrap(HashError, "") return nil, HashError
} }
machineExist, err := c.Ent.Machine. machineExist, err := c.Ent.Machine.
@ -78,7 +78,7 @@ func (c *Client) QueryMachineByID(machineID string) (*ent.Machine, error) {
func (c *Client) ListMachines() ([]*ent.Machine, error) { func (c *Client) ListMachines() ([]*ent.Machine, error) {
machines, err := c.Ent.Machine.Query().All(c.CTX) machines, err := c.Ent.Machine.Query().All(c.CTX)
if err != nil { if err != nil {
return []*ent.Machine{}, errors.Wrapf(QueryFail, "listing machines: %s", err) return nil, errors.Wrapf(QueryFail, "listing machines: %s", err)
} }
return machines, nil return machines, nil
} }
@ -101,7 +101,7 @@ func (c *Client) QueryPendingMachine() ([]*ent.Machine, error) {
machines, err = c.Ent.Machine.Query().Where(machine.IsValidatedEQ(false)).All(c.CTX) machines, err = c.Ent.Machine.Query().Where(machine.IsValidatedEQ(false)).All(c.CTX)
if err != nil { if err != nil {
c.Log.Warningf("QueryPendingMachine : %s", err) c.Log.Warningf("QueryPendingMachine : %s", err)
return []*ent.Machine{}, errors.Wrapf(QueryFail, "querying pending machines: %s", err) return nil, errors.Wrapf(QueryFail, "querying pending machines: %s", err)
} }
return machines, nil return machines, nil
} }
@ -190,12 +190,13 @@ func (c *Client) IsMachineRegistered(machineID string) (bool, error) {
return true, nil return true, nil
} }
if len(exist) > 1 { if len(exist) > 1 {
return false, fmt.Errorf("More than one item with the same machineID in database") return false, fmt.Errorf("more than one item with the same machineID in database")
} }
return false, nil return false, nil
} }
func (c *Client) QueryLastValidatedHeartbeatLT(t time.Time) ([]*ent.Machine, error) { func (c *Client) QueryLastValidatedHeartbeatLT(t time.Time) ([]*ent.Machine, error) {
return c.Ent.Machine.Query().Where(machine.LastHeartbeatLT(t), machine.IsValidatedEQ(true)).All(c.CTX) return c.Ent.Machine.Query().Where(machine.LastHeartbeatLT(t), machine.IsValidatedEQ(true)).All(c.CTX)
} }

View file

@ -227,3 +227,16 @@ teardown() {
assert_stderr --partial "crowdsec init: while loading acquisition config: no datasource enabled" assert_stderr --partial "crowdsec init: while loading acquisition config: no datasource enabled"
} }
@test "crowdsec -t (error in acquisition file)" {
# we can verify the acquisition configuration without running crowdsec
ACQUIS_YAML=$(config_get '.crowdsec_service.acquisition_path')
config_set "$ACQUIS_YAML" 'del(.filenames)'
rune -1 wait-for "${CROWDSEC}"
assert_stderr --partial "failed to configure datasource file: no filename or filenames configuration provided"
config_set "$ACQUIS_YAML" '.filenames=["file.log"]'
config_set "$ACQUIS_YAML" '.meh=3'
rune -1 wait-for "${CROWDSEC}"
assert_stderr --partial "field meh not found in type fileacquisition.FileConfiguration"
}

View file

@ -23,20 +23,24 @@ teardown() {
#---------- #----------
@test "can list machines as regular user" {
rune -0 cscli machines list
}
@test "we have exactly one machine" { @test "we have exactly one machine" {
rune -0 cscli machines list -o json rune -0 cscli machines list -o json
rune -0 jq -c '[. | length, .[0].machineId[0:32], .[0].isValidated]' <(output) rune -0 jq -c '[. | length, .[0].machineId[0:32], .[0].isValidated]' <(output)
assert_output '[1,"githubciXXXXXXXXXXXXXXXXXXXXXXXX",true]' assert_output '[1,"githubciXXXXXXXXXXXXXXXXXXXXXXXX",true]'
} }
@test "don't overwrite local credentials by default" {
rune -1 cscli machines add local -a -o json
rune -0 jq -r '.msg' <(stderr)
assert_output --partial 'already exists: please remove it, use "--force" or specify a different file with "-f"'
rune -0 cscli machines add local -a --force
assert_stderr --partial "Machine 'local' successfully added to the local API"
}
@test "add a new machine and delete it" { @test "add a new machine and delete it" {
rune -0 cscli machines add -a -f /dev/null CiTestMachine -o human rune -0 cscli machines add -a -f /dev/null CiTestMachine -o human
assert_stderr --partial "Machine 'CiTestMachine' successfully added to the local API" assert_stderr --partial "Machine 'CiTestMachine' successfully added to the local API"
assert_stderr --partial "API credentials dumped to '/dev/null'" assert_stderr --partial "API credentials written to '/dev/null'"
# we now have two machines # we now have two machines
rune -0 cscli machines list -o json rune -0 cscli machines list -o json
@ -56,7 +60,7 @@ teardown() {
@test "register, validate and then remove a machine" { @test "register, validate and then remove a machine" {
rune -0 cscli lapi register --machine CiTestMachineRegister -f /dev/null -o human rune -0 cscli lapi register --machine CiTestMachineRegister -f /dev/null -o human
assert_stderr --partial "Successfully registered to Local API (LAPI)" assert_stderr --partial "Successfully registered to Local API (LAPI)"
assert_stderr --partial "Local API credentials dumped to '/dev/null'" assert_stderr --partial "Local API credentials written to '/dev/null'"
# the machine is not validated yet # the machine is not validated yet
rune -0 cscli machines list -o json rune -0 cscli machines list -o json

View file

@ -61,12 +61,12 @@ make_init_data() {
./instance-db config-yaml ./instance-db config-yaml
./instance-db setup ./instance-db setup
./bin/preload-hub-items
# when installed packages are always using sqlite, so no need to regenerate # when installed packages are always using sqlite, so no need to regenerate
# local credz for sqlite # local credz for sqlite
./bin/preload-hub-items [[ "${DB_BACKEND}" == "sqlite" ]] || ${CSCLI} machines add githubciXXXXXXXXXXXXXXXXXXXXXXXX --auto --force
[[ "${DB_BACKEND}" == "sqlite" ]] || ${CSCLI} machines add --auto
mkdir -p "$LOCAL_INIT_DIR" mkdir -p "$LOCAL_INIT_DIR"

View file

@ -115,11 +115,12 @@ make_init_data() {
./instance-db config-yaml ./instance-db config-yaml
./instance-db setup ./instance-db setup
"$CSCLI" --warning machines add githubciXXXXXXXXXXXXXXXXXXXXXXXX --auto
"$CSCLI" --warning hub update "$CSCLI" --warning hub update
./bin/preload-hub-items ./bin/preload-hub-items
"$CSCLI" --warning machines add githubciXXXXXXXXXXXXXXXXXXXXXXXX --auto --force
mkdir -p "$LOCAL_INIT_DIR" mkdir -p "$LOCAL_INIT_DIR"
./instance-db dump "${LOCAL_INIT_DIR}/database" ./instance-db dump "${LOCAL_INIT_DIR}/database"