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 }