bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/pkg/acquisition/acquisition.go (about) 1 package acquisition 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 8 "bitbucket.org/Aishee/synsec/pkg/csconfig" 9 "bitbucket.org/Aishee/synsec/pkg/types" 10 "github.com/pkg/errors" 11 "github.com/prometheus/client_golang/prometheus" 12 log "github.com/sirupsen/logrus" 13 "gopkg.in/yaml.v2" 14 15 tomb "gopkg.in/tomb.v2" 16 ) 17 18 var ReaderHits = prometheus.NewCounterVec( 19 prometheus.CounterOpts{ 20 Name: "cs_reader_hits_total", 21 Help: "Total lines where read.", 22 }, 23 []string{"source"}, 24 ) 25 26 /* 27 current limits : 28 - The acquisition is not yet modular (cf. traefik/yaegi), but we start with an interface to pave the road for it. 29 - The configuration item unmarshaled (DataSourceCfg) isn't generic neither yet. 30 - This changes should be made when we're ready to have acquisition managed by the hub & ccscli 31 once this change is done, we might go for the following configuration format instead : 32 ```yaml 33 --- 34 type: nginx 35 source: journald 36 filter: "PROG=nginx" 37 --- 38 type: nginx 39 source: files 40 filenames: 41 - "/var/log/nginx/*.log" 42 ``` 43 */ 44 45 /* Approach 46 47 We support acquisition in two modes : 48 - tail mode : we're following a stream of info (tail -f $src). this is used when monitoring live logs 49 - cat mode : we're reading a file/source one-shot (cat $src), and scenarios will match the timestamp extracted from logs. 50 51 One DataSourceCfg can lead to multiple goroutines, hence the Tombs passing around to allow proper tracking. 52 tail mode shouldn't return except on errors or when externally killed via tombs. 53 cat mode will return once source has been exhausted. 54 55 56 TBD in current iteration : 57 - how to deal with "file was not present at startup but might appear later" ? 58 */ 59 60 var TAIL_MODE = "tail" 61 var CAT_MODE = "cat" 62 63 type DataSourceCfg struct { 64 Mode string `yaml:"mode,omitempty"` //tail|cat|... 65 Filename string `yaml:"filename,omitempty"` 66 Filenames []string `yaml:"filenames,omitempty"` 67 JournalctlFilters []string `yaml:"journalctl_filter,omitempty"` 68 Labels map[string]string `yaml:"labels,omitempty"` 69 Profiling bool `yaml:"profiling,omitempty"` 70 } 71 72 type DataSource interface { 73 Configure(DataSourceCfg) error 74 /*the readers must watch the tomb (especially in tail mode) to know when to shutdown. 75 tomb is as well used to trigger general shutdown when a datasource errors */ 76 StartReading(chan types.Event, *tomb.Tomb) error 77 Mode() string //return CAT_MODE or TAIL_MODE 78 //Not sure it makes sense to make those funcs part of the interface. 79 //While 'cat' and 'tail' are the only two modes we see now, other modes might appear 80 //StartTail(chan types.Event, *tomb.Tomb) error 81 //StartCat(chan types.Event, *tomb.Tomb) error 82 } 83 84 func DataSourceConfigure(config DataSourceCfg) (DataSource, error) { 85 if config.Mode == "" { /*default mode is tail*/ 86 config.Mode = TAIL_MODE 87 } 88 89 if len(config.Filename) > 0 || len(config.Filenames) > 0 { /*it's file acquisition*/ 90 91 fileSrc := new(FileSource) 92 if err := fileSrc.Configure(config); err != nil { 93 return nil, errors.Wrap(err, "configuring file datasource") 94 } 95 return fileSrc, nil 96 } else if len(config.JournalctlFilters) > 0 { /*it's journald acquisition*/ 97 98 journaldSrc := new(JournaldSource) 99 if err := journaldSrc.Configure(config); err != nil { 100 return nil, errors.Wrap(err, "configuring journald datasource") 101 } 102 return journaldSrc, nil 103 } else { 104 return nil, fmt.Errorf("empty filename(s) and journalctl filter, malformed datasource") 105 } 106 } 107 108 func LoadAcquisitionFromFile(config *csconfig.SynsecServiceCfg) ([]DataSource, error) { 109 110 var sources []DataSource 111 var acquisSources = config.AcquisitionFiles 112 113 for _, acquisFile := range acquisSources { 114 log.Infof("loading acquisition file : %s", acquisFile) 115 yamlFile, err := os.Open(acquisFile) 116 if err != nil { 117 return nil, errors.Wrapf(err, "can't open %s", acquisFile) 118 } 119 dec := yaml.NewDecoder(yamlFile) 120 dec.SetStrict(true) 121 for { 122 sub := DataSourceCfg{} 123 err = dec.Decode(&sub) 124 if err != nil { 125 if err == io.EOF { 126 log.Tracef("End of yaml file") 127 break 128 } 129 return nil, errors.Wrap(err, fmt.Sprintf("failed to yaml decode %s", acquisFile)) 130 } 131 src, err := DataSourceConfigure(sub) 132 if err != nil { 133 log.Warningf("while configuring datasource : %s", err) 134 continue 135 } 136 sources = append(sources, src) 137 } 138 } 139 140 return sources, nil 141 } 142 143 func StartAcquisition(sources []DataSource, output chan types.Event, AcquisTomb *tomb.Tomb) error { 144 145 for i := 0; i < len(sources); i++ { 146 subsrc := sources[i] //ensure its a copy 147 log.Debugf("starting one source %d/%d ->> %T", i, len(sources), subsrc) 148 AcquisTomb.Go(func() error { 149 defer types.CatchPanic("synsec/acquis") 150 if err := subsrc.StartReading(output, AcquisTomb); err != nil { 151 return err 152 } 153 return nil 154 }) 155 } 156 /*return only when acquisition is over (cat) or never (tail)*/ 157 err := AcquisTomb.Wait() 158 return err 159 }