github.com/crowdsecurity/crowdsec@v1.6.1/pkg/csconfig/api.go (about) 1 package csconfig 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "crypto/x509" 7 "errors" 8 "fmt" 9 "io" 10 "net" 11 "os" 12 "strings" 13 "time" 14 15 log "github.com/sirupsen/logrus" 16 "gopkg.in/yaml.v3" 17 18 "github.com/crowdsecurity/go-cs-lib/ptr" 19 "github.com/crowdsecurity/go-cs-lib/yamlpatch" 20 21 "github.com/crowdsecurity/crowdsec/pkg/apiclient" 22 ) 23 24 type APICfg struct { 25 Client *LocalApiClientCfg `yaml:"client"` 26 Server *LocalApiServerCfg `yaml:"server"` 27 CTI *CTICfg `yaml:"cti"` 28 } 29 30 type ApiCredentialsCfg struct { 31 PapiURL string `yaml:"papi_url,omitempty" json:"papi_url,omitempty"` 32 URL string `yaml:"url,omitempty" json:"url,omitempty"` 33 Login string `yaml:"login,omitempty" json:"login,omitempty"` 34 Password string `yaml:"password,omitempty" json:"-"` 35 CACertPath string `yaml:"ca_cert_path,omitempty"` 36 KeyPath string `yaml:"key_path,omitempty"` 37 CertPath string `yaml:"cert_path,omitempty"` 38 } 39 40 /*global api config (for lapi->oapi)*/ 41 type OnlineApiClientCfg struct { 42 CredentialsFilePath string `yaml:"credentials_path,omitempty"` // credz will be edited by software, store in diff file 43 Credentials *ApiCredentialsCfg `yaml:"-"` 44 } 45 46 /*local api config (for crowdsec/cscli->lapi)*/ 47 type LocalApiClientCfg struct { 48 CredentialsFilePath string `yaml:"credentials_path,omitempty"` // credz will be edited by software, store in diff file 49 Credentials *ApiCredentialsCfg `yaml:"-"` 50 InsecureSkipVerify *bool `yaml:"insecure_skip_verify"` // check if api certificate is bad or not 51 } 52 53 type CTICfg struct { 54 Key *string `yaml:"key,omitempty"` 55 CacheTimeout *time.Duration `yaml:"cache_timeout,omitempty"` 56 CacheSize *int `yaml:"cache_size,omitempty"` 57 Enabled *bool `yaml:"enabled,omitempty"` 58 LogLevel *log.Level `yaml:"log_level,omitempty"` 59 } 60 61 func (a *CTICfg) Load() error { 62 if a.Key == nil { 63 *a.Enabled = false 64 } 65 66 if a.Key != nil && *a.Key == "" { 67 return errors.New("empty cti key") 68 } 69 70 if a.Enabled == nil { 71 a.Enabled = new(bool) 72 *a.Enabled = true 73 } 74 75 if a.CacheTimeout == nil { 76 a.CacheTimeout = new(time.Duration) 77 *a.CacheTimeout = 10 * time.Minute 78 } 79 80 if a.CacheSize == nil { 81 a.CacheSize = new(int) 82 *a.CacheSize = 100 83 } 84 85 return nil 86 } 87 88 func (o *OnlineApiClientCfg) Load() error { 89 o.Credentials = new(ApiCredentialsCfg) 90 91 fcontent, err := os.ReadFile(o.CredentialsFilePath) 92 if err != nil { 93 return err 94 } 95 96 dec := yaml.NewDecoder(bytes.NewReader(fcontent)) 97 dec.KnownFields(true) 98 99 err = dec.Decode(o.Credentials) 100 if err != nil { 101 if !errors.Is(err, io.EOF) { 102 return fmt.Errorf("failed unmarshaling api server credentials configuration file '%s': %w", o.CredentialsFilePath, err) 103 } 104 } 105 106 switch { 107 case o.Credentials.Login == "": 108 log.Warningf("can't load CAPI credentials from '%s' (missing login field)", o.CredentialsFilePath) 109 o.Credentials = nil 110 case o.Credentials.Password == "": 111 log.Warningf("can't load CAPI credentials from '%s' (missing password field)", o.CredentialsFilePath) 112 o.Credentials = nil 113 case o.Credentials.URL == "": 114 log.Warningf("can't load CAPI credentials from '%s' (missing url field)", o.CredentialsFilePath) 115 o.Credentials = nil 116 } 117 118 return nil 119 } 120 121 func (l *LocalApiClientCfg) Load() error { 122 patcher := yamlpatch.NewPatcher(l.CredentialsFilePath, ".local") 123 124 fcontent, err := patcher.MergedPatchContent() 125 if err != nil { 126 return err 127 } 128 129 dec := yaml.NewDecoder(bytes.NewReader(fcontent)) 130 dec.KnownFields(true) 131 132 err = dec.Decode(&l.Credentials) 133 if err != nil { 134 if !errors.Is(err, io.EOF) { 135 return fmt.Errorf("failed unmarshaling api client credential configuration file '%s': %w", l.CredentialsFilePath, err) 136 } 137 } 138 139 if l.Credentials == nil || l.Credentials.URL == "" { 140 return fmt.Errorf("no credentials or URL found in api client configuration '%s'", l.CredentialsFilePath) 141 } 142 143 if l.Credentials != nil && l.Credentials.URL != "" { 144 // don't append a trailing slash if the URL is a unix socket 145 if strings.HasPrefix(l.Credentials.URL, "http") && !strings.HasSuffix(l.Credentials.URL, "/") { 146 l.Credentials.URL += "/" 147 } 148 } 149 150 // is the configuration asking for client authentication via TLS? 151 credTLSClientAuth := l.Credentials.CertPath != "" || l.Credentials.KeyPath != "" 152 153 // is the configuration asking for TLS encryption and server authentication? 154 credTLS := credTLSClientAuth || l.Credentials.CACertPath != "" 155 156 credSocket := strings.HasPrefix(l.Credentials.URL, "/") 157 158 if credTLS && credSocket { 159 return errors.New("cannot use TLS with a unix socket") 160 } 161 162 if credTLSClientAuth && l.Credentials.Login != "" { 163 return errors.New("user/password authentication and TLS authentication are mutually exclusive") 164 } 165 166 if l.InsecureSkipVerify == nil { 167 apiclient.InsecureSkipVerify = false 168 } else { 169 apiclient.InsecureSkipVerify = *l.InsecureSkipVerify 170 } 171 172 if l.Credentials.CACertPath != "" { 173 caCert, err := os.ReadFile(l.Credentials.CACertPath) 174 if err != nil { 175 return fmt.Errorf("failed to load cacert: %w", err) 176 } 177 178 caCertPool, err := x509.SystemCertPool() 179 if err != nil { 180 log.Warningf("Error loading system CA certificates: %s", err) 181 } 182 183 if caCertPool == nil { 184 caCertPool = x509.NewCertPool() 185 } 186 187 caCertPool.AppendCertsFromPEM(caCert) 188 apiclient.CaCertPool = caCertPool 189 } 190 191 if l.Credentials.CertPath != "" && l.Credentials.KeyPath != "" { 192 cert, err := tls.LoadX509KeyPair(l.Credentials.CertPath, l.Credentials.KeyPath) 193 if err != nil { 194 return fmt.Errorf("failed to load api client certificate: %w", err) 195 } 196 197 apiclient.Cert = &cert 198 } 199 200 return nil 201 } 202 203 func (c *LocalApiServerCfg) GetTrustedIPs() ([]net.IPNet, error) { 204 trustedIPs := make([]net.IPNet, 0) 205 206 for _, ip := range c.TrustedIPs { 207 cidr := toValidCIDR(ip) 208 209 _, ipNet, err := net.ParseCIDR(cidr) 210 if err != nil { 211 return nil, err 212 } 213 214 trustedIPs = append(trustedIPs, *ipNet) 215 } 216 217 return trustedIPs, nil 218 } 219 220 func toValidCIDR(ip string) string { 221 if strings.Contains(ip, "/") { 222 return ip 223 } 224 225 if strings.Contains(ip, ":") { 226 return ip + "/128" 227 } 228 229 return ip + "/32" 230 } 231 232 type CapiWhitelist struct { 233 Ips []net.IP `yaml:"ips,omitempty"` 234 Cidrs []*net.IPNet `yaml:"cidrs,omitempty"` 235 } 236 237 /*local api service configuration*/ 238 type LocalApiServerCfg struct { 239 Enable *bool `yaml:"enable"` 240 ListenURI string `yaml:"listen_uri,omitempty"` // 127.0.0.1:8080 241 ListenSocket string `yaml:"listen_socket,omitempty"` 242 TLS *TLSCfg `yaml:"tls"` 243 DbConfig *DatabaseCfg `yaml:"-"` 244 LogDir string `yaml:"-"` 245 LogMedia string `yaml:"-"` 246 OnlineClient *OnlineApiClientCfg `yaml:"online_client"` 247 ProfilesPath string `yaml:"profiles_path,omitempty"` 248 ConsoleConfigPath string `yaml:"console_path,omitempty"` 249 ConsoleConfig *ConsoleConfig `yaml:"-"` 250 Profiles []*ProfileCfg `yaml:"-"` 251 LogLevel *log.Level `yaml:"log_level"` 252 UseForwardedForHeaders bool `yaml:"use_forwarded_for_headers,omitempty"` 253 TrustedProxies *[]string `yaml:"trusted_proxies,omitempty"` 254 CompressLogs *bool `yaml:"-"` 255 LogMaxSize int `yaml:"-"` 256 LogMaxAge int `yaml:"-"` 257 LogMaxFiles int `yaml:"-"` 258 TrustedIPs []string `yaml:"trusted_ips,omitempty"` 259 PapiLogLevel *log.Level `yaml:"papi_log_level"` 260 DisableRemoteLapiRegistration bool `yaml:"disable_remote_lapi_registration,omitempty"` 261 CapiWhitelistsPath string `yaml:"capi_whitelists_path,omitempty"` 262 CapiWhitelists *CapiWhitelist `yaml:"-"` 263 } 264 265 func (c *LocalApiServerCfg) ClientURL() string { 266 if c == nil { 267 return "" 268 } 269 270 if c.ListenSocket != "" { 271 return c.ListenSocket 272 } 273 274 if c.ListenURI != "" { 275 return "http://" + c.ListenURI 276 } 277 278 return "" 279 } 280 281 func (c *Config) LoadAPIServer(inCli bool) error { 282 if c.DisableAPI { 283 log.Warning("crowdsec local API is disabled from flag") 284 } 285 286 if c.API.Server == nil { 287 log.Warning("crowdsec local API is disabled") 288 289 c.DisableAPI = true 290 291 return nil 292 } 293 294 if c.API.Server.Enable == nil { 295 // if the option is not present, it is enabled by default 296 c.API.Server.Enable = ptr.Of(true) 297 } 298 299 if !*c.API.Server.Enable { 300 log.Warning("crowdsec local API is disabled because 'enable' is set to false") 301 302 c.DisableAPI = true 303 } 304 305 if c.DisableAPI { 306 return nil 307 } 308 309 if c.API.Server.ListenURI == "" && c.API.Server.ListenSocket == "" { 310 return errors.New("no listen_uri or listen_socket specified") 311 } 312 313 // inherit log level from common, then api->server 314 var logLevel log.Level 315 if c.API.Server.LogLevel != nil { 316 logLevel = *c.API.Server.LogLevel 317 } else if c.Common.LogLevel != nil { 318 logLevel = *c.Common.LogLevel 319 } else { 320 logLevel = log.InfoLevel 321 } 322 323 if c.API.Server.PapiLogLevel == nil { 324 c.API.Server.PapiLogLevel = &logLevel 325 } 326 327 if c.API.Server.OnlineClient != nil && c.API.Server.OnlineClient.CredentialsFilePath != "" { 328 if err := c.API.Server.OnlineClient.Load(); err != nil { 329 return fmt.Errorf("loading online client credentials: %w", err) 330 } 331 } 332 333 if (c.API.Server.OnlineClient == nil || c.API.Server.OnlineClient.Credentials == nil) && !inCli { 334 log.Printf("push and pull to Central API disabled") 335 } 336 337 if err := c.LoadDBConfig(inCli); err != nil { 338 return err 339 } 340 341 if err := c.API.Server.LoadCapiWhitelists(); err != nil { 342 return err 343 } 344 345 if c.API.Server.CapiWhitelistsPath != "" && !inCli { 346 log.Infof("loaded capi whitelist from %s: %d IPs, %d CIDRs", c.API.Server.CapiWhitelistsPath, len(c.API.Server.CapiWhitelists.Ips), len(c.API.Server.CapiWhitelists.Cidrs)) 347 } 348 349 c.API.Server.LogDir = c.Common.LogDir 350 c.API.Server.LogMedia = c.Common.LogMedia 351 c.API.Server.CompressLogs = c.Common.CompressLogs 352 c.API.Server.LogMaxSize = c.Common.LogMaxSize 353 c.API.Server.LogMaxAge = c.Common.LogMaxAge 354 c.API.Server.LogMaxFiles = c.Common.LogMaxFiles 355 356 if c.API.Server.UseForwardedForHeaders && c.API.Server.TrustedProxies == nil { 357 c.API.Server.TrustedProxies = &[]string{"0.0.0.0/0"} 358 } 359 360 if c.API.Server.TrustedProxies != nil { 361 c.API.Server.UseForwardedForHeaders = true 362 } 363 364 if err := c.API.Server.LoadProfiles(); err != nil { 365 return fmt.Errorf("while loading profiles for LAPI: %w", err) 366 } 367 368 if c.API.Server.ConsoleConfigPath == "" { 369 c.API.Server.ConsoleConfigPath = DefaultConsoleConfigFilePath 370 } 371 372 if err := c.API.Server.LoadConsoleConfig(); err != nil { 373 return fmt.Errorf("while loading console options: %w", err) 374 } 375 376 if c.API.CTI != nil { 377 if err := c.API.CTI.Load(); err != nil { 378 return fmt.Errorf("loading CTI configuration: %w", err) 379 } 380 } 381 382 return nil 383 } 384 385 // we cannot unmarshal to type net.IPNet, so we need to do it manually 386 type capiWhitelists struct { 387 Ips []string `yaml:"ips"` 388 Cidrs []string `yaml:"cidrs"` 389 } 390 391 func parseCapiWhitelists(fd io.Reader) (*CapiWhitelist, error) { 392 fromCfg := capiWhitelists{} 393 394 decoder := yaml.NewDecoder(fd) 395 if err := decoder.Decode(&fromCfg); err != nil { 396 if errors.Is(err, io.EOF) { 397 return nil, errors.New("empty file") 398 } 399 400 return nil, err 401 } 402 403 ret := &CapiWhitelist{ 404 Ips: make([]net.IP, len(fromCfg.Ips)), 405 Cidrs: make([]*net.IPNet, len(fromCfg.Cidrs)), 406 } 407 408 for idx, v := range fromCfg.Ips { 409 ip := net.ParseIP(v) 410 if ip == nil { 411 return nil, fmt.Errorf("invalid IP address: %s", v) 412 } 413 414 ret.Ips[idx] = ip 415 } 416 417 for idx, v := range fromCfg.Cidrs { 418 _, tnet, err := net.ParseCIDR(v) 419 if err != nil { 420 return nil, err 421 } 422 423 ret.Cidrs[idx] = tnet 424 } 425 426 return ret, nil 427 } 428 429 func (c *LocalApiServerCfg) LoadCapiWhitelists() error { 430 if c.CapiWhitelistsPath == "" { 431 return nil 432 } 433 434 fd, err := os.Open(c.CapiWhitelistsPath) 435 if err != nil { 436 return fmt.Errorf("while opening capi whitelist file: %w", err) 437 } 438 439 defer fd.Close() 440 441 c.CapiWhitelists, err = parseCapiWhitelists(fd) 442 if err != nil { 443 return fmt.Errorf("while parsing capi whitelist file '%s': %w", c.CapiWhitelistsPath, err) 444 } 445 446 return nil 447 } 448 449 func (c *Config) LoadAPIClient() error { 450 if c.API == nil || c.API.Client == nil || c.API.Client.CredentialsFilePath == "" || c.DisableAgent { 451 return errors.New("no API client section in configuration") 452 } 453 454 if err := c.API.Client.Load(); err != nil { 455 return err 456 } 457 458 return nil 459 }