2020-07-16 14:05:03 +00:00
package database
import (
2020-11-30 09:37:17 +00:00
"context"
2022-01-20 10:17:21 +00:00
"database/sql"
2020-07-16 14:05:03 +00:00
"fmt"
2021-02-02 13:15:13 +00:00
"os"
2020-07-16 14:05:03 +00:00
"time"
2022-03-23 08:29:22 +00:00
"entgo.io/ent/dialect"
2022-01-20 10:17:21 +00:00
entsql "entgo.io/ent/dialect/sql"
2020-11-30 09:37:17 +00:00
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
2020-07-16 14:05:03 +00:00
"github.com/crowdsecurity/crowdsec/pkg/types"
2020-11-30 09:37:17 +00:00
"github.com/go-co-op/gocron"
_ "github.com/go-sql-driver/mysql"
2022-01-20 10:17:21 +00:00
_ "github.com/jackc/pgx/v4/stdlib"
2020-11-30 09:37:17 +00:00
_ "github.com/lib/pq"
2020-07-16 14:05:03 +00:00
_ "github.com/mattn/go-sqlite3"
2020-11-30 09:37:17 +00:00
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
2020-07-16 14:05:03 +00:00
)
2020-11-30 09:37:17 +00:00
type Client struct {
2021-12-15 10:39:37 +00:00
Ent * ent . Client
CTX context . Context
Log * log . Logger
CanFlush bool
2020-07-16 14:05:03 +00:00
}
2022-03-23 08:29:22 +00:00
func getEntDriver ( dbtype string , dbdialect string , dsn string , config * csconfig . DatabaseCfg ) ( * entsql . Driver , error ) {
2022-02-17 16:52:04 +00:00
db , err := sql . Open ( dbtype , dsn )
if err != nil {
return nil , err
}
if config . MaxOpenConns == nil {
log . Warningf ( "MaxOpenConns is 0, defaulting to %d" , csconfig . DEFAULT_MAX_OPEN_CONNS )
config . MaxOpenConns = types . IntPtr ( csconfig . DEFAULT_MAX_OPEN_CONNS )
}
db . SetMaxOpenConns ( * config . MaxOpenConns )
2022-03-23 08:29:22 +00:00
drv := entsql . OpenDB ( dbdialect , db )
2022-02-17 16:52:04 +00:00
return drv , nil
}
2020-11-30 09:37:17 +00:00
func NewClient ( config * csconfig . DatabaseCfg ) ( * Client , error ) {
var client * ent . Client
var err error
if config == nil {
return & Client { } , fmt . Errorf ( "DB config is empty" )
}
2021-08-25 09:45:29 +00:00
/*The logger that will be used by db operations*/
clog := log . New ( )
if err := types . ConfigureLogger ( clog ) ; err != nil {
return nil , errors . Wrap ( err , "while configuring db logger" )
}
if config . LogLevel != nil {
clog . SetLevel ( * config . LogLevel )
}
entLogger := clog . WithField ( "context" , "ent" )
entOpt := ent . Log ( entLogger . Debug )
2020-11-30 09:37:17 +00:00
switch config . Type {
2020-07-16 14:05:03 +00:00
case "sqlite" :
2021-02-02 13:15:13 +00:00
/*if it's the first startup, we want to touch and chmod file*/
if _ , err := os . Stat ( config . DbPath ) ; os . IsNotExist ( err ) {
f , err := os . OpenFile ( config . DbPath , os . O_CREATE | os . O_RDWR , 0600 )
if err != nil {
return & Client { } , errors . Wrapf ( err , "failed to create SQLite database file %q" , config . DbPath )
}
if err := f . Close ( ) ; err != nil {
return & Client { } , errors . Wrapf ( err , "failed to create SQLite database file %q" , config . DbPath )
}
2022-09-28 14:18:00 +00:00
}
//Always try to set permissions to simplify a bit the code for windows (as the permissions set by OpenFile will be garbage)
if err := setFilePerm ( config . DbPath , 0600 ) ; err != nil {
return & Client { } , fmt . Errorf ( "unable to set perms on %s: %v" , config . DbPath , err )
2021-02-02 13:15:13 +00:00
}
2022-09-14 13:09:54 +00:00
if config . UseWal == nil {
entLogger . Warn ( "you are using sqlite without WAL, this can have an impact of performance. If you do not store the database in a network share, set db_config.use_wal to true. Set explicitly to false to disable this warning." )
}
var sqliteConnectionStringParameters string
if config . UseWal != nil && * config . UseWal {
sqliteConnectionStringParameters = "_busy_timeout=100000&_fk=1&_journal_mode=WAL"
} else {
sqliteConnectionStringParameters = "_busy_timeout=100000&_fk=1"
}
drv , err := getEntDriver ( "sqlite3" , dialect . SQLite , fmt . Sprintf ( "file:%s?%s" , config . DbPath , sqliteConnectionStringParameters ) , config )
2020-11-30 09:37:17 +00:00
if err != nil {
2022-02-17 16:52:04 +00:00
return & Client { } , errors . Wrapf ( err , "failed opening connection to sqlite: %v" , config . DbPath )
2020-07-16 14:05:03 +00:00
}
2022-02-17 16:52:04 +00:00
client = ent . NewClient ( ent . Driver ( drv ) , entOpt )
2020-07-16 14:05:03 +00:00
case "mysql" :
2022-03-23 08:29:22 +00:00
drv , err := getEntDriver ( "mysql" , dialect . MySQL , fmt . Sprintf ( "%s:%s@tcp(%s:%d)/%s?parseTime=True" , config . User , config . Password , config . Host , config . Port , config . DbName ) , config )
2020-11-30 09:37:17 +00:00
if err != nil {
return & Client { } , fmt . Errorf ( "failed opening connection to mysql: %v" , err )
2020-07-16 14:05:03 +00:00
}
2022-02-17 16:52:04 +00:00
client = ent . NewClient ( ent . Driver ( drv ) , entOpt )
2020-11-30 09:37:17 +00:00
case "postgres" , "postgresql" :
2022-03-23 08:29:22 +00:00
drv , err := getEntDriver ( "postgres" , dialect . Postgres , fmt . Sprintf ( "host=%s port=%d user=%s dbname=%s password=%s sslmode=%s" , config . Host , config . Port , config . User , config . DbName , config . Password , config . Sslmode ) , config )
2020-11-30 09:37:17 +00:00
if err != nil {
2022-02-17 16:52:04 +00:00
return & Client { } , fmt . Errorf ( "failed opening connection to postgresql: %v" , err )
2020-07-16 14:05:03 +00:00
}
2022-02-17 16:52:04 +00:00
client = ent . NewClient ( ent . Driver ( drv ) , entOpt )
2022-01-20 10:17:21 +00:00
case "pgx" :
2022-03-23 08:29:22 +00:00
drv , err := getEntDriver ( "pgx" , dialect . Postgres , fmt . Sprintf ( "postgresql://%s:%s@%s:%d/%s?sslmode=%s" , config . User , config . Password , config . Host , config . Port , config . DbName , config . Sslmode ) , config )
2022-01-20 10:17:21 +00:00
if err != nil {
return & Client { } , fmt . Errorf ( "failed opening connection to pgx: %v" , err )
}
client = ent . NewClient ( ent . Driver ( drv ) , entOpt )
2020-07-16 14:05:03 +00:00
default :
2022-05-19 08:48:08 +00:00
return & Client { } , fmt . Errorf ( "unknown database type '%s'" , config . Type )
2020-07-16 14:05:03 +00:00
}
2021-08-25 09:45:29 +00:00
if config . LogLevel != nil && * config . LogLevel >= log . DebugLevel {
clog . Debugf ( "Enabling request debug" )
client = client . Debug ( )
2020-07-16 14:05:03 +00:00
}
2020-11-30 09:37:17 +00:00
if err = client . Schema . Create ( context . Background ( ) ) ; err != nil {
return nil , fmt . Errorf ( "failed creating schema resources: %v" , err )
2020-07-16 14:05:03 +00:00
}
2021-12-15 10:39:37 +00:00
return & Client { Ent : client , CTX : context . Background ( ) , Log : clog , CanFlush : true } , nil
2020-11-30 09:37:17 +00:00
}
2020-07-16 14:05:03 +00:00
2020-11-30 09:37:17 +00:00
func ( c * Client ) StartFlushScheduler ( config * csconfig . FlushDBCfg ) ( * gocron . Scheduler , error ) {
maxItems := 0
maxAge := ""
if config . MaxItems != nil && * config . MaxItems <= 0 {
return nil , fmt . Errorf ( "max_items can't be zero or negative number" )
2020-07-16 14:05:03 +00:00
}
2020-11-30 09:37:17 +00:00
if config . MaxItems != nil {
maxItems = * config . MaxItems
2020-07-16 14:05:03 +00:00
}
2020-11-30 09:37:17 +00:00
if config . MaxAge != nil && * config . MaxAge != "" {
maxAge = * config . MaxAge
2020-07-16 14:05:03 +00:00
}
2022-06-08 14:05:52 +00:00
// Init & Start cronjob every minute for alerts
2020-11-30 09:37:17 +00:00
scheduler := gocron . NewScheduler ( time . UTC )
2022-06-08 14:05:52 +00:00
job , err := scheduler . Every ( 1 ) . Minute ( ) . Do ( c . FlushAlerts , maxAge , maxItems )
if err != nil {
return nil , errors . Wrap ( err , "while starting FlushAlerts scheduler" )
}
2021-10-26 11:33:45 +00:00
job . SingletonMode ( )
2022-06-08 14:05:52 +00:00
// Init & Start cronjob every hour for bouncers/agents
if config . AgentsGC != nil {
if config . AgentsGC . Cert != nil {
duration , err := types . ParseDuration ( * config . AgentsGC . Cert )
if err != nil {
return nil , errors . Wrap ( err , "while parsing agents cert auto-delete duration" )
}
config . AgentsGC . CertDuration = & duration
}
if config . AgentsGC . LoginPassword != nil {
duration , err := types . ParseDuration ( * config . AgentsGC . LoginPassword )
if err != nil {
return nil , errors . Wrap ( err , "while parsing agents login/password auto-delete duration" )
}
config . AgentsGC . LoginPasswordDuration = & duration
}
if config . AgentsGC . Api != nil {
2022-06-22 07:38:23 +00:00
log . Warning ( "agents auto-delete for API auth is not supported (use cert or login_password)" )
2022-06-08 14:05:52 +00:00
}
}
if config . BouncersGC != nil {
if config . BouncersGC . Cert != nil {
duration , err := types . ParseDuration ( * config . BouncersGC . Cert )
if err != nil {
return nil , errors . Wrap ( err , "while parsing bouncers cert auto-delete duration" )
}
config . BouncersGC . CertDuration = & duration
}
if config . BouncersGC . Api != nil {
duration , err := types . ParseDuration ( * config . BouncersGC . Api )
if err != nil {
return nil , errors . Wrap ( err , "while parsing bouncers api auto-delete duration" )
}
config . BouncersGC . ApiDuration = & duration
}
if config . BouncersGC . LoginPassword != nil {
2022-06-22 07:38:23 +00:00
log . Warning ( "bouncers auto-delete for login/password auth is not supported (use cert or api)" )
2022-06-08 14:05:52 +00:00
}
}
baJob , err := scheduler . Every ( 1 ) . Minute ( ) . Do ( c . FlushAgentsAndBouncers , config . AgentsGC , config . BouncersGC )
if err != nil {
return nil , errors . Wrap ( err , "while starting FlushAgentsAndBouncers scheduler" )
}
baJob . SingletonMode ( )
2020-11-30 09:37:17 +00:00
scheduler . StartAsync ( )
2020-07-16 14:05:03 +00:00
2020-11-30 09:37:17 +00:00
return scheduler , nil
2020-07-16 14:05:03 +00:00
}