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  }