github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/syslog/syslog.go (about)

     1  package syslogacquisition
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/prometheus/client_golang/prometheus"
    10  	log "github.com/sirupsen/logrus"
    11  	"gopkg.in/tomb.v2"
    12  	"gopkg.in/yaml.v2"
    13  
    14  	"github.com/crowdsecurity/go-cs-lib/trace"
    15  
    16  	"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
    17  	"github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/syslog/internal/parser/rfc3164"
    18  	"github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/syslog/internal/parser/rfc5424"
    19  	syslogserver "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/syslog/internal/server"
    20  	"github.com/crowdsecurity/crowdsec/pkg/types"
    21  )
    22  
    23  type SyslogConfiguration struct {
    24  	Proto                             string `yaml:"protocol,omitempty"`
    25  	Port                              int    `yaml:"listen_port,omitempty"`
    26  	Addr                              string `yaml:"listen_addr,omitempty"`
    27  	MaxMessageLen                     int    `yaml:"max_message_len,omitempty"`
    28  	configuration.DataSourceCommonCfg `yaml:",inline"`
    29  }
    30  
    31  type SyslogSource struct {
    32  	metricsLevel int
    33  	config       SyslogConfiguration
    34  	logger       *log.Entry
    35  	server       *syslogserver.SyslogServer
    36  	serverTomb   *tomb.Tomb
    37  }
    38  
    39  var linesReceived = prometheus.NewCounterVec(
    40  	prometheus.CounterOpts{
    41  		Name: "cs_syslogsource_hits_total",
    42  		Help: "Total lines that were received.",
    43  	},
    44  	[]string{"source"})
    45  
    46  var linesParsed = prometheus.NewCounterVec(
    47  	prometheus.CounterOpts{
    48  		Name: "cs_syslogsource_parsed_total",
    49  		Help: "Total lines that were successfully parsed",
    50  	},
    51  	[]string{"source", "type"})
    52  
    53  func (s *SyslogSource) GetUuid() string {
    54  	return s.config.UniqueId
    55  }
    56  
    57  func (s *SyslogSource) GetName() string {
    58  	return "syslog"
    59  }
    60  
    61  func (s *SyslogSource) GetMode() string {
    62  	return s.config.Mode
    63  }
    64  
    65  func (s *SyslogSource) Dump() interface{} {
    66  	return s
    67  }
    68  
    69  func (s *SyslogSource) CanRun() error {
    70  	return nil
    71  }
    72  
    73  func (s *SyslogSource) GetMetrics() []prometheus.Collector {
    74  	return []prometheus.Collector{linesReceived, linesParsed}
    75  }
    76  
    77  func (s *SyslogSource) GetAggregMetrics() []prometheus.Collector {
    78  	return []prometheus.Collector{linesReceived, linesParsed}
    79  }
    80  
    81  func (s *SyslogSource) ConfigureByDSN(dsn string, labels map[string]string, logger *log.Entry, uuid string) error {
    82  	return fmt.Errorf("syslog datasource does not support one shot acquisition")
    83  }
    84  
    85  func (s *SyslogSource) OneShotAcquisition(out chan types.Event, t *tomb.Tomb) error {
    86  	return fmt.Errorf("syslog datasource does not support one shot acquisition")
    87  }
    88  
    89  func validatePort(port int) bool {
    90  	return port > 0 && port <= 65535
    91  }
    92  
    93  func validateAddr(addr string) bool {
    94  	return net.ParseIP(addr) != nil
    95  }
    96  
    97  func (s *SyslogSource) UnmarshalConfig(yamlConfig []byte) error {
    98  	s.config = SyslogConfiguration{}
    99  	s.config.Mode = configuration.TAIL_MODE
   100  
   101  	err := yaml.UnmarshalStrict(yamlConfig, &s.config)
   102  	if err != nil {
   103  		return fmt.Errorf("cannot parse syslog configuration: %w", err)
   104  	}
   105  
   106  	if s.config.Addr == "" {
   107  		s.config.Addr = "127.0.0.1" //do we want a usable or secure default ?
   108  	}
   109  	if s.config.Port == 0 {
   110  		s.config.Port = 514
   111  	}
   112  	if s.config.MaxMessageLen == 0 {
   113  		s.config.MaxMessageLen = 2048
   114  	}
   115  	if !validatePort(s.config.Port) {
   116  		return fmt.Errorf("invalid port %d", s.config.Port)
   117  	}
   118  	if !validateAddr(s.config.Addr) {
   119  		return fmt.Errorf("invalid listen IP %s", s.config.Addr)
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  func (s *SyslogSource) Configure(yamlConfig []byte, logger *log.Entry, MetricsLevel int) error {
   126  	s.logger = logger
   127  	s.logger.Infof("Starting syslog datasource configuration")
   128  	s.metricsLevel = MetricsLevel
   129  	err := s.UnmarshalConfig(yamlConfig)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  func (s *SyslogSource) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error {
   138  	c := make(chan syslogserver.SyslogMessage)
   139  	s.server = &syslogserver.SyslogServer{Logger: s.logger.WithField("syslog", "internal"), MaxMessageLen: s.config.MaxMessageLen}
   140  	s.server.SetChannel(c)
   141  	err := s.server.Listen(s.config.Addr, s.config.Port)
   142  	if err != nil {
   143  		return fmt.Errorf("could not start syslog server: %w", err)
   144  	}
   145  	s.serverTomb = s.server.StartServer()
   146  	t.Go(func() error {
   147  		defer trace.CatchPanic("crowdsec/acquis/syslog/live")
   148  		return s.handleSyslogMsg(out, t, c)
   149  	})
   150  	return nil
   151  }
   152  
   153  func (s *SyslogSource) buildLogFromSyslog(ts time.Time, hostname string,
   154  	appname string, pid string, msg string) string {
   155  	ret := ""
   156  	if !ts.IsZero() {
   157  		ret += ts.Format("Jan 2 15:04:05")
   158  	} else {
   159  		s.logger.Tracef("%s - missing TS", msg)
   160  		ret += time.Now().UTC().Format("Jan 2 15:04:05")
   161  	}
   162  	if hostname != "" {
   163  		ret += " " + hostname
   164  	} else {
   165  		s.logger.Tracef("%s - missing host", msg)
   166  		ret += " unknownhost"
   167  	}
   168  	if appname != "" {
   169  		ret += " " + appname
   170  	}
   171  	if pid != "" {
   172  		ret += "[" + pid + "]: "
   173  	} else {
   174  		ret += ": "
   175  	}
   176  	if msg != "" {
   177  		ret += msg
   178  	}
   179  	return ret
   180  
   181  }
   182  
   183  func (s *SyslogSource) handleSyslogMsg(out chan types.Event, t *tomb.Tomb, c chan syslogserver.SyslogMessage) error {
   184  	killed := false
   185  	for {
   186  		select {
   187  		case <-t.Dying():
   188  			if !killed {
   189  				s.logger.Info("Syslog datasource is dying")
   190  				s.serverTomb.Kill(nil)
   191  				killed = true
   192  			}
   193  		case <-s.serverTomb.Dead():
   194  			s.logger.Info("Syslog server has exited")
   195  			return nil
   196  		case syslogLine := <-c:
   197  			var line string
   198  			var ts time.Time
   199  
   200  			logger := s.logger.WithField("client", syslogLine.Client)
   201  			logger.Tracef("raw: %s", syslogLine)
   202  			if s.metricsLevel != configuration.METRICS_NONE {
   203  				linesReceived.With(prometheus.Labels{"source": syslogLine.Client}).Inc()
   204  			}
   205  			p := rfc3164.NewRFC3164Parser(rfc3164.WithCurrentYear())
   206  			err := p.Parse(syslogLine.Message)
   207  			if err != nil {
   208  				logger.Debugf("could not parse as RFC3164 (%s)", err)
   209  				p2 := rfc5424.NewRFC5424Parser()
   210  				err = p2.Parse(syslogLine.Message)
   211  				if err != nil {
   212  					logger.Errorf("could not parse message: %s", err)
   213  					logger.Debugf("could not parse as RFC5424 (%s) : %s", err, syslogLine.Message)
   214  					continue
   215  				}
   216  				line = s.buildLogFromSyslog(p2.Timestamp, p2.Hostname, p2.Tag, p2.PID, p2.Message)
   217  				if s.metricsLevel != configuration.METRICS_NONE {
   218  					linesParsed.With(prometheus.Labels{"source": syslogLine.Client, "type": "rfc5424"}).Inc()
   219  				}
   220  			} else {
   221  				line = s.buildLogFromSyslog(p.Timestamp, p.Hostname, p.Tag, p.PID, p.Message)
   222  				if s.metricsLevel != configuration.METRICS_NONE {
   223  					linesParsed.With(prometheus.Labels{"source": syslogLine.Client, "type": "rfc3164"}).Inc()
   224  				}
   225  			}
   226  
   227  			line = strings.TrimSuffix(line, "\n")
   228  
   229  			l := types.Line{}
   230  			l.Raw = line
   231  			l.Module = s.GetName()
   232  			l.Labels = s.config.Labels
   233  			l.Time = ts
   234  			l.Src = syslogLine.Client
   235  			l.Process = true
   236  			if !s.config.UseTimeMachine {
   237  				out <- types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: types.LIVE}
   238  			} else {
   239  				out <- types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: types.TIMEMACHINE}
   240  			}
   241  		}
   242  	}
   243  }