198 lines
3.9 KiB
Go
198 lines
3.9 KiB
Go
package rules
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"iwarma.ru/console/correlator/events"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"iwarma.ru/console/correlator/config"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
const (
|
|
BashActionType = "bash"
|
|
)
|
|
|
|
type BashAction struct {
|
|
Body string `json:"body"`
|
|
|
|
bodyTemplate *template.Template
|
|
}
|
|
|
|
func (action *BashAction) GetType() string {
|
|
return BashActionType
|
|
}
|
|
|
|
func (action BashAction) ToInterface() (map[string]interface{}, error) {
|
|
result := make(map[string]interface{})
|
|
|
|
result["body"] = action.Body
|
|
result["type"] = BashActionType
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (action BashAction) MarshalJSON() ([]byte, error) {
|
|
data, err := action.ToInterface()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return json.Marshal(data)
|
|
}
|
|
|
|
func (action *BashAction) UnmarshalJSON(b []byte) error {
|
|
cur := struct {
|
|
CurType string `json:"type"`
|
|
Body string `json:"body"`
|
|
}{}
|
|
|
|
err := json.Unmarshal(b, &cur)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cur.CurType != BashActionType {
|
|
return fmt.Errorf("bad action type, Expect %v, got %v", BashActionType, cur.CurType)
|
|
}
|
|
|
|
action.Body = cur.Body
|
|
|
|
return nil
|
|
}
|
|
|
|
func (action *BashAction) ParseInterface(v interface{}) error {
|
|
m, ok := v.(map[string]interface{})
|
|
if !ok {
|
|
return fmt.Errorf("can't parse %v from %T", v, v)
|
|
}
|
|
|
|
body, ok := m["body"].(string)
|
|
if !ok {
|
|
return errors.New("no body")
|
|
}
|
|
|
|
t, ok := m["type"].(string)
|
|
if !ok {
|
|
return errors.New("no type")
|
|
}
|
|
|
|
if t != BashActionType {
|
|
return fmt.Errorf("bad type, expect %v got %v", BashActionType, t)
|
|
}
|
|
|
|
action.Body = body
|
|
|
|
return nil
|
|
}
|
|
|
|
// See https://stackoverflow.com/a/52594719
|
|
func removeLBR(text string) string {
|
|
re := regexp.MustCompile(`\x{000D}\x{000A}|[\x{000A}\x{000B}\x{000C}\x{000D}\x{0085}\x{2028}\x{2029}]`)
|
|
return re.ReplaceAllString(text, "\n")
|
|
}
|
|
|
|
func (action *BashAction) Perform(events *[]*events.Event) error {
|
|
cl := log.WithFields(log.Fields{"type": BashActionType, "event_count": len(*events)})
|
|
cl.Debug("Start action")
|
|
defer cl.Debug("End action")
|
|
|
|
var err error
|
|
|
|
// Check if we need to prepare template
|
|
if action.bodyTemplate == nil {
|
|
text := strings.Replace(action.Body, `\r\n`, "\n", -1)
|
|
text = removeLBR(text)
|
|
|
|
action.bodyTemplate, err = template.New("Body").Parse(text)
|
|
if err != nil {
|
|
cl.Errorf("Can't create body template: %v", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Actual action
|
|
for _, event := range *events {
|
|
|
|
// Render script body
|
|
body, err := renderTemplate(action.bodyTemplate, event)
|
|
if err != nil {
|
|
cl.Errorf("Can't render script body: %v", err)
|
|
return err
|
|
}
|
|
|
|
// Create temp file
|
|
file, err := ioutil.TempFile("", "action.*.sh")
|
|
if err != nil {
|
|
cl.Errorf("Can't create script file: %v", err)
|
|
return err
|
|
}
|
|
|
|
// Remove temp script
|
|
defer func() {
|
|
err = os.Remove(file.Name())
|
|
if err != nil {
|
|
cl.Errorf("Can't remove temp script: %v", err)
|
|
}
|
|
}()
|
|
|
|
count, err := file.Write([]byte(body))
|
|
if err != nil {
|
|
cl.Errorf("Can't write script body: %v", err)
|
|
return err
|
|
}
|
|
|
|
if count != len([]byte(body)) {
|
|
cl.Errorf("Write bad bytes count, expec %v, got %v", len([]byte(body)), count)
|
|
}
|
|
|
|
err = file.Close()
|
|
if err != nil {
|
|
cl.Errorf("Can't close temp script: %v", err)
|
|
}
|
|
|
|
err = os.Chmod(file.Name(), 0775)
|
|
if err != nil {
|
|
cl.Errorf("Can't set script permissions: %v", err)
|
|
return err
|
|
}
|
|
|
|
if viper.GetBool(config.DebugDumpRequest) {
|
|
info, err := os.Stat(file.Name())
|
|
if os.IsNotExist(err) {
|
|
cl.Errorf("Script %v does not exist #0", file.Name())
|
|
}
|
|
cl.Debugf("File mode: %v", info.Mode())
|
|
cl.Debugf("File size: %v", info.Size())
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
cmd := exec.Command(file.Name())
|
|
cmd.Stdout = &buf
|
|
cmd.Stderr = &buf
|
|
|
|
// TODO: Maybe set to goroutine
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
cl.Errorf("Script execution error: %v", err)
|
|
return err
|
|
}
|
|
|
|
if viper.GetBool(config.Verbose) {
|
|
cl.Infof("Script output:\n%v", buf.String())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|