369 lines
8.5 KiB
Go
369 lines
8.5 KiB
Go
package rules
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"iwarma.ru/console/correlator/events"
|
|
"net/http"
|
|
"strconv"
|
|
"text/template"
|
|
|
|
"iwarma.ru/console/correlator/config"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
const (
|
|
IncidentActionType = "incident"
|
|
)
|
|
|
|
type IncidentAction struct {
|
|
Title string `json:"title"` // template
|
|
Comment string `json:"comment"` // template
|
|
Effects []string `json:"effects"`
|
|
Category string `json:"category"`
|
|
Importance string `json:"importance"`
|
|
Assigned string `json:"assigned_to"`
|
|
Description string `json:"description"` // template
|
|
CloseRecommendations []string `json:"close_recommendations"`
|
|
|
|
// Private
|
|
token string
|
|
client *http.Client
|
|
|
|
template struct {
|
|
title *template.Template
|
|
comment *template.Template
|
|
description *template.Template
|
|
}
|
|
}
|
|
|
|
func (action IncidentAction) GetType() string {
|
|
return IncidentActionType
|
|
}
|
|
|
|
func (action IncidentAction) ToInterface() (map[string]interface{}, error) {
|
|
result := make(map[string]interface{})
|
|
|
|
result["title"] = action.Title
|
|
|
|
if action.Comment != "" {
|
|
result["comment"] = action.Comment
|
|
}
|
|
|
|
// Effects
|
|
if len(action.Effects) > 0 {
|
|
effects := make([]interface{}, 0)
|
|
for _, cur := range action.Effects {
|
|
tmp, err := strconv.Atoi(cur)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
effects = append(effects, tmp)
|
|
}
|
|
result["effects"] = effects
|
|
}
|
|
|
|
// Category
|
|
if action.Category != "" {
|
|
catPk, err := strconv.Atoi(action.Category)
|
|
result["category"] = catPk
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Importance
|
|
imp, err := strconv.Atoi(action.Importance)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result["importance"] = imp
|
|
|
|
// Assigned
|
|
if action.Assigned != "" {
|
|
ass, err := strconv.Atoi(action.Assigned)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result["assigned_to"] = ass
|
|
}
|
|
|
|
// Description
|
|
if action.Description != "" {
|
|
result["description"] = action.Description
|
|
}
|
|
|
|
// Close recommendations
|
|
if len(action.CloseRecommendations) > 0 {
|
|
closeRec := make([]interface{}, 0)
|
|
for _, cur := range action.CloseRecommendations {
|
|
tmp, err := strconv.Atoi(cur)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
closeRec = append(closeRec, tmp)
|
|
}
|
|
result["close_recommendations"] = closeRec
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (action IncidentAction) MarshalJSON() ([]byte, error) {
|
|
data, err := action.ToInterface()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return json.Marshal(data)
|
|
}
|
|
|
|
func (action *IncidentAction) UnmarshalJSON(b []byte) error {
|
|
var data interface{}
|
|
err := json.Unmarshal(b, &data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return action.ParseInterface(data)
|
|
}
|
|
|
|
func (action *IncidentAction) ParseInterface(v interface{}) error {
|
|
m, ok := v.(map[string]interface{})
|
|
if !ok {
|
|
return fmt.Errorf("can't parse %v from %T", v, v)
|
|
}
|
|
|
|
t, ok := m["type"].(string)
|
|
if !ok {
|
|
return errors.New("no type")
|
|
}
|
|
|
|
if t != IncidentActionType {
|
|
return fmt.Errorf("got bad type: %v", t)
|
|
}
|
|
|
|
action.Title, ok = m["title"].(string)
|
|
if !ok {
|
|
return errors.New("no title")
|
|
}
|
|
|
|
// We can process without comment and description
|
|
action.Comment, _ = m["comment"].(string)
|
|
action.Description, _ = m["description"].(string)
|
|
|
|
if w, ok := m["effects"]; ok {
|
|
switch v := w.(type) {
|
|
case []interface{}:
|
|
{
|
|
for _, cur := range v {
|
|
switch w := cur.(type) {
|
|
case string:
|
|
action.Effects = append(action.Effects, w)
|
|
case float64:
|
|
action.Effects = append(action.Effects, fmt.Sprintf("%v", w))
|
|
default:
|
|
return fmt.Errorf("bad effect type: %T with value %v in interface %v", cur, cur, m)
|
|
}
|
|
}
|
|
}
|
|
case string:
|
|
action.Effects = append(action.Effects, v)
|
|
case float64:
|
|
action.Effects = append(action.Effects, fmt.Sprintf("%v", v))
|
|
default:
|
|
return fmt.Errorf("bad effect type: %T with value %v in interface %v", v, v, m)
|
|
}
|
|
}
|
|
|
|
action.Category, _ = m["category"].(string)
|
|
action.Importance, ok = m["importance"].(string)
|
|
if !ok {
|
|
return errors.New("no importance")
|
|
}
|
|
|
|
action.Assigned, _ = m["assigned_to"].(string)
|
|
|
|
if w, ok := m["close_recommendations"]; ok {
|
|
switch v := w.(type) {
|
|
case []interface{}:
|
|
{
|
|
for _, cur := range v {
|
|
switch w := cur.(type) {
|
|
case string:
|
|
action.CloseRecommendations = append(action.CloseRecommendations, w)
|
|
case float64:
|
|
action.CloseRecommendations = append(action.CloseRecommendations, fmt.Sprintf("%v", w))
|
|
case nil:
|
|
break
|
|
default:
|
|
return fmt.Errorf("bad recommendations type: %T with value %v in interface %v", cur, cur, m)
|
|
}
|
|
}
|
|
}
|
|
case string:
|
|
action.CloseRecommendations = append(action.CloseRecommendations, v)
|
|
case float64:
|
|
action.CloseRecommendations = append(action.CloseRecommendations, fmt.Sprintf("%v", v))
|
|
default:
|
|
return fmt.Errorf("bad close recommendations type: %T with value %v in interface %v", v, v, m)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (action *IncidentAction) Perform(events *[]*events.Event) error {
|
|
cl := log.WithFields(log.Fields{"type": IncidentActionType, "event_count": len(*events)})
|
|
cl.Debug("Start action")
|
|
defer cl.Debug("End action")
|
|
|
|
if events == nil || len(*events) == 0 {
|
|
cl.Error("No events")
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
|
|
// Check if we need to prepare templates
|
|
if action.template.title == nil {
|
|
action.template.title, err = template.New("Title").Parse(action.Title)
|
|
if err != nil {
|
|
cl.Errorf("Can't create title template: %v", err)
|
|
return err
|
|
}
|
|
|
|
action.template.comment, err = template.New("Comment").Parse(action.Comment)
|
|
if err != nil {
|
|
cl.Errorf("Can't create comment template: %v", err)
|
|
return err
|
|
}
|
|
|
|
action.template.description, err = template.New("Description").Parse(action.Description)
|
|
if err != nil {
|
|
cl.Errorf("Can't create description template: %v", err)
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
// Check if we need to prepare http client
|
|
if action.client == nil {
|
|
if viper.GetBool(config.ConsoleIgnoreSSLErrors) {
|
|
transport := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
action.client = &http.Client{Transport: transport}
|
|
} else {
|
|
action.client = &http.Client{}
|
|
}
|
|
}
|
|
|
|
// Get auth token
|
|
if action.token == "" {
|
|
action.token, err = ObtainAuthToken()
|
|
if err != nil {
|
|
cl.Errorf("Can't get auth token: %v", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// For this type of action, multi means that we need to add all events
|
|
// to created incident
|
|
|
|
// Prepare body
|
|
ibody, err := action.ToInterface()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cur := *events
|
|
event := cur[0]
|
|
|
|
// Render templates
|
|
if _, ok := ibody["title"]; ok {
|
|
s, err := renderTemplate(action.template.title, event)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Need to truncate title, so serializer will accept it
|
|
if len(s) > 256 {
|
|
s = s[:255]
|
|
}
|
|
|
|
ibody["title"] = s
|
|
}
|
|
|
|
if _, ok := ibody["comment"]; ok {
|
|
ibody["comment"], err = renderTemplate(action.template.comment, event)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if _, ok := ibody["description"]; ok {
|
|
ibody["description"], err = renderTemplate(action.template.description, event)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Add type to connect incident with sensor
|
|
ibody["sensor"] = event.GetString("type")
|
|
|
|
// Insert incidents info
|
|
// If we have a single-event rule, len of events will be 1
|
|
ibody["event_count"] = len(*events)
|
|
ibody["events"] = events
|
|
|
|
jsonBody, err := json.Marshal(ibody)
|
|
if err != nil {
|
|
cl.Errorf("Can't serialize body: %v", err)
|
|
return err
|
|
}
|
|
|
|
request, err := http.NewRequest("POST", viper.GetString(config.ConsoleUrlIncident), bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
cl.Errorf("Can't create request: %v", err)
|
|
return err
|
|
}
|
|
|
|
// Set headers
|
|
request.Header.Set("Authorization", fmt.Sprintf("Token %v", action.token))
|
|
request.Header.Set("Content-type", "application/json")
|
|
|
|
// Do request
|
|
response, err := action.client.Do(request)
|
|
if err != nil {
|
|
cl.Errorf("Can't send request: %v", err)
|
|
return err
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
// Check result
|
|
body, err := ioutil.ReadAll(response.Body)
|
|
if err != nil {
|
|
cl.Errorf("Can't read response body: %v", err)
|
|
return err
|
|
}
|
|
|
|
// Dump request and response
|
|
if viper.GetBool(config.DebugDumpRequest) {
|
|
go DumpNetwork("incident", *request.URL, ibody, body, response.StatusCode)
|
|
}
|
|
|
|
if response.StatusCode != http.StatusCreated {
|
|
cl.Errorf("Bad status code, expect %v, got %v", http.StatusCreated, response.Status)
|
|
return fmt.Errorf("bad status code, expect %v, got %v", http.StatusCreated, response.Status)
|
|
}
|
|
|
|
return nil
|
|
}
|