github.com/crowdsecurity/crowdsec@v1.6.1/cmd/crowdsec/main.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"flag"
     6  	"fmt"
     7  	_ "net/http/pprof"
     8  	"os"
     9  	"runtime"
    10  	"runtime/pprof"
    11  	"strings"
    12  	"time"
    13  
    14  	log "github.com/sirupsen/logrus"
    15  	"gopkg.in/tomb.v2"
    16  
    17  	"github.com/crowdsecurity/crowdsec/pkg/acquisition"
    18  	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
    19  	"github.com/crowdsecurity/crowdsec/pkg/csplugin"
    20  	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
    21  	"github.com/crowdsecurity/crowdsec/pkg/cwversion"
    22  	"github.com/crowdsecurity/crowdsec/pkg/fflag"
    23  	"github.com/crowdsecurity/crowdsec/pkg/leakybucket"
    24  	"github.com/crowdsecurity/crowdsec/pkg/parser"
    25  	"github.com/crowdsecurity/crowdsec/pkg/types"
    26  )
    27  
    28  var (
    29  	/*tombs for the parser, buckets and outputs.*/
    30  	acquisTomb   tomb.Tomb
    31  	parsersTomb  tomb.Tomb
    32  	bucketsTomb  tomb.Tomb
    33  	outputsTomb  tomb.Tomb
    34  	apiTomb      tomb.Tomb
    35  	crowdsecTomb tomb.Tomb
    36  	pluginTomb   tomb.Tomb
    37  
    38  	flags *Flags
    39  
    40  	/*the state of acquisition*/
    41  	dataSources []acquisition.DataSource
    42  	/*the state of the buckets*/
    43  	holders []leakybucket.BucketFactory
    44  	buckets *leakybucket.Buckets
    45  
    46  	inputLineChan   chan types.Event
    47  	inputEventChan  chan types.Event
    48  	outputEventChan chan types.Event // the buckets init returns its own chan that is used for multiplexing
    49  	/*settings*/
    50  	lastProcessedItem time.Time /*keep track of last item timestamp in time-machine. it is used to GC buckets when we dump them.*/
    51  	pluginBroker      csplugin.PluginBroker
    52  )
    53  
    54  type Flags struct {
    55  	ConfigFile string
    56  
    57  	LogLevelTrace bool
    58  	LogLevelDebug bool
    59  	LogLevelInfo  bool
    60  	LogLevelWarn  bool
    61  	LogLevelError bool
    62  	LogLevelFatal bool
    63  
    64  	PrintVersion   bool
    65  	SingleFileType string
    66  	Labels         map[string]string
    67  	OneShotDSN     string
    68  	TestMode       bool
    69  	DisableAgent   bool
    70  	DisableAPI     bool
    71  	WinSvc         string
    72  	DisableCAPI    bool
    73  	Transform      string
    74  	OrderEvent     bool
    75  	CPUProfile     string
    76  }
    77  
    78  func (f *Flags) haveTimeMachine() bool {
    79  	return f.OneShotDSN != ""
    80  }
    81  
    82  type labelsMap map[string]string
    83  
    84  func LoadBuckets(cConfig *csconfig.Config, hub *cwhub.Hub) error {
    85  	var (
    86  		err   error
    87  		files []string
    88  	)
    89  
    90  	for _, hubScenarioItem := range hub.GetItemMap(cwhub.SCENARIOS) {
    91  		if hubScenarioItem.State.Installed {
    92  			files = append(files, hubScenarioItem.State.LocalPath)
    93  		}
    94  	}
    95  
    96  	buckets = leakybucket.NewBuckets()
    97  
    98  	log.Infof("Loading %d scenario files", len(files))
    99  	holders, outputEventChan, err = leakybucket.LoadBuckets(cConfig.Crowdsec, hub, files, &bucketsTomb, buckets, flags.OrderEvent)
   100  
   101  	if err != nil {
   102  		return fmt.Errorf("scenario loading failed: %w", err)
   103  	}
   104  
   105  	if cConfig.Prometheus != nil && cConfig.Prometheus.Enabled {
   106  		for holderIndex := range holders {
   107  			holders[holderIndex].Profiling = true
   108  		}
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  func LoadAcquisition(cConfig *csconfig.Config) ([]acquisition.DataSource, error) {
   115  	var err error
   116  
   117  	if flags.SingleFileType != "" && flags.OneShotDSN != "" {
   118  		flags.Labels = labels
   119  		flags.Labels["type"] = flags.SingleFileType
   120  
   121  		dataSources, err = acquisition.LoadAcquisitionFromDSN(flags.OneShotDSN, flags.Labels, flags.Transform)
   122  		if err != nil {
   123  			return nil, fmt.Errorf("failed to configure datasource for %s: %w", flags.OneShotDSN, err)
   124  		}
   125  	} else {
   126  		dataSources, err = acquisition.LoadAcquisitionFromFile(cConfig.Crowdsec, cConfig.Prometheus)
   127  		if err != nil {
   128  			return nil, err
   129  		}
   130  	}
   131  
   132  	if len(dataSources) == 0 {
   133  		return nil, errors.New("no datasource enabled")
   134  	}
   135  
   136  	return dataSources, nil
   137  }
   138  
   139  var (
   140  	dumpFolder string
   141  	dumpStates bool
   142  	labels     = make(labelsMap)
   143  )
   144  
   145  func (l *labelsMap) String() string {
   146  	return "labels"
   147  }
   148  
   149  func (l labelsMap) Set(label string) error {
   150  	for _, pair := range strings.Split(label, ",") {
   151  		split := strings.Split(pair, ":")
   152  		if len(split) != 2 {
   153  			return fmt.Errorf("invalid format for label '%s', must be key:value", pair)
   154  		}
   155  
   156  		l[split[0]] = split[1]
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  func (f *Flags) Parse() {
   163  	flag.StringVar(&f.ConfigFile, "c", csconfig.DefaultConfigPath("config.yaml"), "configuration file")
   164  
   165  	flag.BoolVar(&f.LogLevelTrace, "trace", false, "set log level to 'trace' (VERY verbose)")
   166  	flag.BoolVar(&f.LogLevelDebug, "debug", false, "set log level to 'debug'")
   167  	flag.BoolVar(&f.LogLevelInfo, "info", false, "set log level to 'info'")
   168  	flag.BoolVar(&f.LogLevelWarn, "warning", false, "set log level to 'warning'")
   169  	flag.BoolVar(&f.LogLevelError, "error", false, "set log level to 'error'")
   170  	flag.BoolVar(&f.LogLevelFatal, "fatal", false, "set log level to 'fatal'")
   171  
   172  	flag.BoolVar(&f.PrintVersion, "version", false, "display version")
   173  	flag.StringVar(&f.OneShotDSN, "dsn", "", "Process a single data source in time-machine")
   174  	flag.StringVar(&f.Transform, "transform", "", "expr to apply on the event after acquisition")
   175  	flag.StringVar(&f.SingleFileType, "type", "", "Labels.type for file in time-machine")
   176  	flag.Var(&labels, "label", "Additional Labels for file in time-machine")
   177  	flag.BoolVar(&f.TestMode, "t", false, "only test configs")
   178  	flag.BoolVar(&f.DisableAgent, "no-cs", false, "disable crowdsec agent")
   179  	flag.BoolVar(&f.DisableAPI, "no-api", false, "disable local API")
   180  	flag.BoolVar(&f.DisableCAPI, "no-capi", false, "disable communication with Central API")
   181  	flag.BoolVar(&f.OrderEvent, "order-event", false, "enforce event ordering with significant performance cost")
   182  
   183  	if runtime.GOOS == "windows" {
   184  		flag.StringVar(&f.WinSvc, "winsvc", "", "Windows service Action: Install, Remove etc..")
   185  	}
   186  
   187  	flag.StringVar(&dumpFolder, "dump-data", "", "dump parsers/buckets raw outputs")
   188  	flag.StringVar(&f.CPUProfile, "cpu-profile", "", "write cpu profile to file")
   189  	flag.Parse()
   190  }
   191  
   192  func newLogLevel(curLevelPtr *log.Level, f *Flags) *log.Level {
   193  	// mother of all defaults
   194  	ret := log.InfoLevel
   195  
   196  	// keep if already set
   197  	if curLevelPtr != nil {
   198  		ret = *curLevelPtr
   199  	}
   200  
   201  	// override from flags
   202  	switch {
   203  	case f.LogLevelTrace:
   204  		ret = log.TraceLevel
   205  	case f.LogLevelDebug:
   206  		ret = log.DebugLevel
   207  	case f.LogLevelInfo:
   208  		ret = log.InfoLevel
   209  	case f.LogLevelWarn:
   210  		ret = log.WarnLevel
   211  	case f.LogLevelError:
   212  		ret = log.ErrorLevel
   213  	case f.LogLevelFatal:
   214  		ret = log.FatalLevel
   215  	default:
   216  	}
   217  
   218  	if curLevelPtr != nil && ret == *curLevelPtr {
   219  		// avoid returning a new ptr to the same value
   220  		return curLevelPtr
   221  	}
   222  
   223  	return &ret
   224  }
   225  
   226  // LoadConfig returns a configuration parsed from configuration file
   227  func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet bool) (*csconfig.Config, error) {
   228  	cConfig, _, err := csconfig.NewConfig(configFile, disableAgent, disableAPI, quiet)
   229  	if err != nil {
   230  		return nil, fmt.Errorf("while loading configuration file: %w", err)
   231  	}
   232  
   233  	cConfig.Common.LogLevel = newLogLevel(cConfig.Common.LogLevel, flags)
   234  
   235  	if dumpFolder != "" {
   236  		parser.ParseDump = true
   237  		parser.DumpFolder = dumpFolder
   238  		leakybucket.BucketPourTrack = true
   239  		dumpStates = true
   240  	}
   241  
   242  	if flags.SingleFileType != "" && flags.OneShotDSN != "" {
   243  		// if we're in time-machine mode, we don't want to log to file
   244  		cConfig.Common.LogMedia = "stdout"
   245  	}
   246  
   247  	// Configure logging
   248  	if err := types.SetDefaultLoggerConfig(cConfig.Common.LogMedia,
   249  		cConfig.Common.LogDir, *cConfig.Common.LogLevel,
   250  		cConfig.Common.LogMaxSize, cConfig.Common.LogMaxFiles,
   251  		cConfig.Common.LogMaxAge, cConfig.Common.CompressLogs,
   252  		cConfig.Common.ForceColorLogs); err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	if cConfig.Common.LogMedia != "stdout" {
   257  		log.AddHook(&FatalHook{
   258  			Writer:    os.Stderr,
   259  			LogLevels: []log.Level{log.FatalLevel, log.PanicLevel},
   260  		})
   261  	}
   262  
   263  	if err := csconfig.LoadFeatureFlagsFile(configFile, log.StandardLogger()); err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	if !cConfig.DisableAgent {
   268  		if err := cConfig.LoadCrowdsec(); err != nil {
   269  			return nil, err
   270  		}
   271  	}
   272  
   273  	if !cConfig.DisableAPI {
   274  		if err := cConfig.LoadAPIServer(false); err != nil {
   275  			return nil, err
   276  		}
   277  	}
   278  
   279  	if !cConfig.DisableAgent && (cConfig.API == nil || cConfig.API.Client == nil || cConfig.API.Client.Credentials == nil) {
   280  		return nil, errors.New("missing local API credentials for crowdsec agent, abort")
   281  	}
   282  
   283  	if cConfig.DisableAPI && cConfig.DisableAgent {
   284  		return nil, errors.New("you must run at least the API Server or crowdsec")
   285  	}
   286  
   287  	if flags.OneShotDSN != "" && flags.SingleFileType == "" {
   288  		return nil, errors.New("-dsn requires a -type argument")
   289  	}
   290  
   291  	if flags.Transform != "" && flags.OneShotDSN == "" {
   292  		return nil, errors.New("-transform requires a -dsn argument")
   293  	}
   294  
   295  	if flags.SingleFileType != "" && flags.OneShotDSN == "" {
   296  		return nil, errors.New("-type requires a -dsn argument")
   297  	}
   298  
   299  	if flags.SingleFileType != "" && flags.OneShotDSN != "" {
   300  		if cConfig.API != nil && cConfig.API.Server != nil {
   301  			cConfig.API.Server.OnlineClient = nil
   302  		}
   303  		/*if the api is disabled as well, just read file and exit, don't daemonize*/
   304  		if cConfig.DisableAPI {
   305  			cConfig.Common.Daemonize = false
   306  		}
   307  
   308  		log.Infof("single file mode : log_media=%s daemonize=%t", cConfig.Common.LogMedia, cConfig.Common.Daemonize)
   309  	}
   310  
   311  	if cConfig.Common.PidDir != "" {
   312  		log.Warn("Deprecation warning: the pid_dir config can be safely removed and is not required")
   313  	}
   314  
   315  	if cConfig.Common.Daemonize && runtime.GOOS == "windows" {
   316  		log.Debug("Daemonization is not supported on Windows, disabling")
   317  
   318  		cConfig.Common.Daemonize = false
   319  	}
   320  
   321  	// recap of the enabled feature flags, because logging
   322  	// was not enabled when we set them from envvars
   323  	if fflist := csconfig.ListFeatureFlags(); fflist != "" {
   324  		log.Infof("Enabled feature flags: %s", fflist)
   325  	}
   326  
   327  	return cConfig, nil
   328  }
   329  
   330  // crowdsecT0 can be used to measure start time of services,
   331  // or uptime of the application
   332  var crowdsecT0 time.Time
   333  
   334  func main() {
   335  	// The initial log level is INFO, even if the user provided an -error or -warning flag
   336  	// because we need feature flags before parsing cli flags
   337  	log.SetFormatter(&log.TextFormatter{TimestampFormat: time.RFC3339, FullTimestamp: true})
   338  
   339  	if err := fflag.RegisterAllFeatures(); err != nil {
   340  		log.Fatalf("failed to register features: %s", err)
   341  	}
   342  
   343  	// some features can require configuration or command-line options,
   344  	// so we need to parse them asap. we'll load from feature.yaml later.
   345  	if err := csconfig.LoadFeatureFlagsEnv(log.StandardLogger()); err != nil {
   346  		log.Fatalf("failed to set feature flags from environment: %s", err)
   347  	}
   348  
   349  	crowdsecT0 = time.Now()
   350  
   351  	log.Debugf("os.Args: %v", os.Args)
   352  
   353  	// Handle command line arguments
   354  	flags = &Flags{}
   355  	flags.Parse()
   356  
   357  	if len(flag.Args()) > 0 {
   358  		fmt.Fprintf(os.Stderr, "argument provided but not defined: %s\n", flag.Args()[0])
   359  		flag.Usage()
   360  		// the flag package exits with 2 in case of unknown flag
   361  		os.Exit(2)
   362  	}
   363  
   364  	if flags.PrintVersion {
   365  		cwversion.Show()
   366  		os.Exit(0)
   367  	}
   368  
   369  	if flags.CPUProfile != "" {
   370  		f, err := os.Create(flags.CPUProfile)
   371  		if err != nil {
   372  			log.Fatalf("could not create CPU profile: %s", err)
   373  		}
   374  
   375  		log.Infof("CPU profile will be written to %s", flags.CPUProfile)
   376  
   377  		if err := pprof.StartCPUProfile(f); err != nil {
   378  			f.Close()
   379  			log.Fatalf("could not start CPU profile: %s", err)
   380  		}
   381  
   382  		defer f.Close()
   383  		defer pprof.StopCPUProfile()
   384  	}
   385  
   386  	err := StartRunSvc()
   387  	if err != nil {
   388  		pprof.StopCPUProfile()
   389  		log.Fatal(err) //nolint:gocritic // Disable warning for the defer pprof.StopCPUProfile() call
   390  	}
   391  
   392  	os.Exit(0)
   393  }