bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/pkg/acquisition/journalctl_reader.go (about) 1 package acquisition 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os/exec" 9 "strings" 10 "time" 11 12 log "github.com/sirupsen/logrus" 13 14 leaky "bitbucket.org/Aishee/synsec/pkg/leakybucket" 15 "bitbucket.org/Aishee/synsec/pkg/types" 16 "github.com/pkg/errors" 17 "github.com/prometheus/client_golang/prometheus" 18 tomb "gopkg.in/tomb.v2" 19 ) 20 21 /* 22 journald/systemd support : 23 24 systemd has its own logging system, which stores files in non-text mode. 25 To be able to read those, we're going to read the output of journalctl, see https://bitbucket.org/Aishee/synsec/issues/423 26 27 28 TBD : 29 - handle journalctl errors 30 */ 31 32 type JournaldSource struct { 33 Config DataSourceCfg 34 Cmd *exec.Cmd 35 Stdout io.ReadCloser 36 Stderr io.ReadCloser 37 Decoder *json.Decoder 38 SrcName string 39 } 40 41 var JOURNALD_CMD = "journalctl" 42 var JOURNALD_DEFAULT_TAIL_ARGS = []string{"--follow"} 43 var JOURNALD_DEFAULT_CAT_ARGS = []string{} 44 45 func (j *JournaldSource) Configure(config DataSourceCfg) error { 46 var journalArgs []string 47 48 j.Config = config 49 if config.JournalctlFilters == nil { 50 return fmt.Errorf("journalctl_filter shouldn't be empty") 51 } 52 53 if j.Config.Mode == TAIL_MODE { 54 journalArgs = JOURNALD_DEFAULT_TAIL_ARGS 55 } else if j.Config.Mode == CAT_MODE { 56 journalArgs = JOURNALD_DEFAULT_CAT_ARGS 57 } else { 58 return fmt.Errorf("unknown mode '%s' for journald source", j.Config.Mode) 59 } 60 journalArgs = append(journalArgs, config.JournalctlFilters...) 61 62 j.Cmd = exec.Command(JOURNALD_CMD, journalArgs...) 63 j.Stderr, _ = j.Cmd.StderrPipe() 64 j.Stdout, _ = j.Cmd.StdoutPipe() 65 j.SrcName = fmt.Sprintf("journalctl-%s", strings.Join(config.JournalctlFilters, ".")) 66 log.Infof("[journald datasource] Configured with filters : %+v", journalArgs) 67 log.Debugf("cmd path : %s", j.Cmd.Path) 68 log.Debugf("cmd args : %+v", j.Cmd.Args) 69 70 return nil 71 } 72 73 func (j *JournaldSource) Mode() string { 74 return j.Config.Mode 75 } 76 77 func (j *JournaldSource) readOutput(out chan types.Event, t *tomb.Tomb) error { 78 79 /* 80 todo : handle the channel 81 */ 82 clog := log.WithFields(log.Fields{ 83 "acquisition file": j.SrcName, 84 }) 85 if err := j.Cmd.Start(); err != nil { 86 clog.Errorf("failed to start journalctl: %s", err) 87 return errors.Wrapf(err, "starting journalctl (%s)", j.SrcName) 88 } 89 90 readErr := make(chan error) 91 92 /*read stderr*/ 93 go func() { 94 scanner := bufio.NewScanner(j.Stderr) 95 if scanner == nil { 96 readErr <- fmt.Errorf("failed to create stderr scanner") 97 return 98 } 99 for scanner.Scan() { 100 txt := scanner.Text() 101 clog.Warningf("got stderr message : %s", txt) 102 readErr <- fmt.Errorf(txt) 103 } 104 }() 105 /*read stdout*/ 106 go func() { 107 scanner := bufio.NewScanner(j.Stdout) 108 if scanner == nil { 109 readErr <- fmt.Errorf("failed to create stdout scanner") 110 return 111 } 112 for scanner.Scan() { 113 l := types.Line{} 114 ReaderHits.With(prometheus.Labels{"source": j.SrcName}).Inc() 115 l.Raw = scanner.Text() 116 clog.Debugf("getting one line : %s", l.Raw) 117 l.Labels = j.Config.Labels 118 l.Time = time.Now() 119 l.Src = j.SrcName 120 l.Process = true 121 evt := types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: leaky.LIVE} 122 out <- evt 123 } 124 clog.Debugf("finished reading from journalctl") 125 if err := scanner.Err(); err != nil { 126 clog.Debugf("got an error while reading %s : %s", j.SrcName, err) 127 readErr <- err 128 return 129 } 130 readErr <- nil 131 }() 132 133 for { 134 select { 135 case <-t.Dying(): 136 clog.Debugf("journalctl datasource %s stopping", j.SrcName) 137 return nil 138 case err := <-readErr: 139 clog.Debugf("the subroutine returned, leave as well") 140 if err != nil { 141 clog.Warningf("journalctl reader error : %s", err) 142 t.Kill(err) 143 } 144 return err 145 } 146 } 147 } 148 149 func (j *JournaldSource) StartReading(out chan types.Event, t *tomb.Tomb) error { 150 151 if j.Config.Mode == CAT_MODE { 152 return j.StartCat(out, t) 153 } else if j.Config.Mode == TAIL_MODE { 154 return j.StartTail(out, t) 155 } else { 156 return fmt.Errorf("unknown mode '%s' for file acquisition", j.Config.Mode) 157 } 158 } 159 160 func (j *JournaldSource) StartCat(out chan types.Event, t *tomb.Tomb) error { 161 t.Go(func() error { 162 defer types.CatchPanic("synsec/acquis/tailjournalctl") 163 return j.readOutput(out, t) 164 }) 165 return nil 166 } 167 168 func (j *JournaldSource) StartTail(out chan types.Event, t *tomb.Tomb) error { 169 t.Go(func() error { 170 defer types.CatchPanic("synsec/acquis/catjournalctl") 171 return j.readOutput(out, t) 172 }) 173 return nil 174 }