github.com/crowdsecurity/crowdsec@v1.6.1/pkg/csconfig/database.go (about)

     1  package csconfig
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"path/filepath"
     7  	"time"
     8  
     9  	"entgo.io/ent/dialect"
    10  	log "github.com/sirupsen/logrus"
    11  
    12  	"github.com/crowdsecurity/go-cs-lib/ptr"
    13  
    14  	"github.com/crowdsecurity/crowdsec/pkg/types"
    15  )
    16  
    17  const (
    18  	DEFAULT_MAX_OPEN_CONNS  = 100
    19  	defaultDecisionBulkSize = 1000
    20  	// we need an upper bound due to the sqlite limit of 32k variables in a query
    21  	// we have 15 variables per decision, so 32768/15 = 2184.5333
    22  	maxDecisionBulkSize = 2000
    23  )
    24  
    25  type DatabaseCfg struct {
    26  	User             string      `yaml:"user"`
    27  	Password         string      `yaml:"password"`
    28  	DbName           string      `yaml:"db_name"`
    29  	Sslmode          string      `yaml:"sslmode"`
    30  	Host             string      `yaml:"host"`
    31  	Port             int         `yaml:"port"`
    32  	DbPath           string      `yaml:"db_path"`
    33  	Type             string      `yaml:"type"`
    34  	Flush            *FlushDBCfg `yaml:"flush"`
    35  	LogLevel         *log.Level  `yaml:"log_level"`
    36  	MaxOpenConns     *int        `yaml:"max_open_conns,omitempty"`
    37  	UseWal           *bool       `yaml:"use_wal,omitempty"`
    38  	DecisionBulkSize int         `yaml:"decision_bulk_size,omitempty"`
    39  }
    40  
    41  type AuthGCCfg struct {
    42  	Cert                  *string `yaml:"cert,omitempty"`
    43  	CertDuration          *time.Duration
    44  	Api                   *string `yaml:"api_key,omitempty"`
    45  	ApiDuration           *time.Duration
    46  	LoginPassword         *string `yaml:"login_password,omitempty"`
    47  	LoginPasswordDuration *time.Duration
    48  }
    49  
    50  type FlushDBCfg struct {
    51  	MaxItems   *int       `yaml:"max_items,omitempty"`
    52  	// We could unmarshal as time.Duration, but alert filters right now are a map of strings
    53  	MaxAge     *string    `yaml:"max_age,omitempty"`
    54  	BouncersGC *AuthGCCfg `yaml:"bouncers_autodelete,omitempty"`
    55  	AgentsGC   *AuthGCCfg `yaml:"agents_autodelete,omitempty"`
    56  }
    57  
    58  func (c *Config) LoadDBConfig(inCli bool) error {
    59  	if c.DbConfig == nil {
    60  		return errors.New("no database configuration provided")
    61  	}
    62  
    63  	if c.Cscli != nil {
    64  		c.Cscli.DbConfig = c.DbConfig
    65  	}
    66  
    67  	if c.API != nil && c.API.Server != nil {
    68  		c.API.Server.DbConfig = c.DbConfig
    69  	}
    70  
    71  	if c.DbConfig.MaxOpenConns == nil {
    72  		c.DbConfig.MaxOpenConns = ptr.Of(DEFAULT_MAX_OPEN_CONNS)
    73  	}
    74  
    75  	if !inCli && c.DbConfig.Type == "sqlite" {
    76  		if c.DbConfig.UseWal == nil {
    77  			dbDir := filepath.Dir(c.DbConfig.DbPath)
    78  			isNetwork, fsType, err := types.IsNetworkFS(dbDir)
    79  			if err != nil {
    80  				log.Warnf("unable to determine if database is on network filesystem: %s", err)
    81  				log.Warning("You are using sqlite without WAL, this can have a performance impact. 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.")
    82  				return nil
    83  			}
    84  			if isNetwork {
    85  				log.Debugf("database is on network filesystem (%s), setting useWal to false", fsType)
    86  				c.DbConfig.UseWal = ptr.Of(false)
    87  			} else {
    88  				log.Debugf("database is on local filesystem (%s), setting useWal to true", fsType)
    89  				c.DbConfig.UseWal = ptr.Of(true)
    90  			}
    91  		} else if *c.DbConfig.UseWal {
    92  			dbDir := filepath.Dir(c.DbConfig.DbPath)
    93  			isNetwork, fsType, err := types.IsNetworkFS(dbDir)
    94  			if err != nil {
    95  				log.Warnf("unable to determine if database is on network filesystem: %s", err)
    96  				return nil
    97  			}
    98  			if isNetwork {
    99  				log.Warnf("database seems to be stored on a network share (%s), but useWal is set to true. Proceed at your own risk.", fsType)
   100  			}
   101  		}
   102  	}
   103  
   104  	if c.DbConfig.DecisionBulkSize == 0 {
   105  		log.Tracef("No decision_bulk_size value provided, using default value of %d", defaultDecisionBulkSize)
   106  		c.DbConfig.DecisionBulkSize = defaultDecisionBulkSize
   107  	}
   108  
   109  	if c.DbConfig.DecisionBulkSize > maxDecisionBulkSize {
   110  		log.Warningf("decision_bulk_size too high (%d), setting to the maximum value of %d", c.DbConfig.DecisionBulkSize, maxDecisionBulkSize)
   111  		c.DbConfig.DecisionBulkSize = maxDecisionBulkSize
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  func (d *DatabaseCfg) ConnectionString() string {
   118  	connString := ""
   119  
   120  	switch d.Type {
   121  	case "sqlite":
   122  		var sqliteConnectionStringParameters string
   123  		if d.UseWal != nil && *d.UseWal {
   124  			sqliteConnectionStringParameters = "_busy_timeout=100000&_fk=1&_journal_mode=WAL"
   125  		} else {
   126  			sqliteConnectionStringParameters = "_busy_timeout=100000&_fk=1"
   127  		}
   128  
   129  		connString = fmt.Sprintf("file:%s?%s", d.DbPath, sqliteConnectionStringParameters)
   130  	case "mysql":
   131  		if d.isSocketConfig() {
   132  			connString = fmt.Sprintf("%s:%s@unix(%s)/%s?parseTime=True", d.User, d.Password, d.DbPath, d.DbName)
   133  		} else {
   134  			connString = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=True", d.User, d.Password, d.Host, d.Port, d.DbName)
   135  		}
   136  	case "postgres", "postgresql", "pgx":
   137  		if d.isSocketConfig() {
   138  			connString = fmt.Sprintf("host=%s user=%s dbname=%s password=%s", d.DbPath, d.User, d.DbName, d.Password)
   139  		} else {
   140  			connString = fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=%s", d.Host, d.Port, d.User, d.DbName, d.Password, d.Sslmode)
   141  		}
   142  	}
   143  
   144  	return connString
   145  }
   146  
   147  func (d *DatabaseCfg) ConnectionDialect() (string, string, error) {
   148  	switch d.Type {
   149  	case "sqlite":
   150  		return "sqlite3", dialect.SQLite, nil
   151  	case "mysql":
   152  		return "mysql", dialect.MySQL, nil
   153  	case "pgx", "postgresql", "postgres":
   154  		if d.Type != "pgx" {
   155  			log.Debugf("database type '%s' is deprecated, switching to 'pgx' instead", d.Type)
   156  		}
   157  
   158  		return "pgx", dialect.Postgres, nil
   159  	}
   160  
   161  	return "", "", fmt.Errorf("unknown database type '%s'", d.Type)
   162  }
   163  
   164  func (d *DatabaseCfg) isSocketConfig() bool {
   165  	return d.Host == "" && d.Port == 0 && d.DbPath != ""
   166  }