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 }