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  }