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 ( AssetActionType = "asset" ) type AssetAction struct { Name string Description string Manufacturer string Model string Ip string Os string Ports string Vulnerabilities []string Group string AssetType string Status string templates struct { name *template.Template description *template.Template model *template.Template ip *template.Template ports *template.Template } client *http.Client token string } func (action *AssetAction) GetType() string { return AssetActionType } func (action AssetAction) ToInterface() (map[string]interface{}, error) { result := make(map[string]interface{}) result["type"] = AssetActionType result["name"] = action.Name result["description"] = action.Description result["manufacturer"] = action.Manufacturer result["model"] = action.Model result["ip"] = action.Ip result["os"] = action.Os result["ports"] = action.Ports if len(action.Vulnerabilities) > 0 { vulnerabilities := make([]interface{}, 0) for _, cur := range action.Vulnerabilities { tmp, err := strconv.Atoi(cur) if err != nil { return nil, err } vulnerabilities = append(vulnerabilities, tmp) } result["vulnerabilities"] = vulnerabilities } result["group"] = action.Group result["asset_type"] = action.AssetType result["status"] = action.Status return result, nil } func (action AssetAction) MarshalJSON() ([]byte, error) { data, err := action.ToInterface() if err != nil { return nil, err } return json.Marshal(data) } func (action *AssetAction) UnmarshalJSON(b []byte) error { var data interface{} err := json.Unmarshal(b, &data) if err != nil { return err } return action.ParseInterface(data) } func (action *AssetAction) 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 != AssetActionType { return fmt.Errorf("bad type, expect %v got %v", ExecActionType, t) } action.Name, ok = m["name"].(string) if !ok { return errors.New("no name") } action.Ip, ok = m["ip"].(string) if !ok { return errors.New("no IP") } // Not required action.Description, _ = m["description"].(string) action.Manufacturer, _ = m["manufacturer"].(string) action.Model, _ = m["model"].(string) action.Os, _ = m["os"].(string) action.Ports, _ = m["ports"].(string) action.Group, _ = m["group"].(string) action.AssetType, _ = m["asset_type"].(string) action.Status, _ = m["status"].(string) // For array if w, ok := m["vulnerabilities"]; ok { switch v := w.(type) { case []interface{}: { for _, cur := range v { switch w := cur.(type) { case string: action.Vulnerabilities = append(action.Vulnerabilities, w) case float64: action.Vulnerabilities = append(action.Vulnerabilities, fmt.Sprintf("%v", w)) default: return fmt.Errorf("bad vulnerabilities type: %T with value %v in interface %v", cur, cur, m) } } } case string: action.Vulnerabilities = append(action.Vulnerabilities, v) case float64: action.Vulnerabilities = append(action.Vulnerabilities, fmt.Sprintf("%v", v)) default: return fmt.Errorf("bad vulnerabilities type: %T with value %v in interface %v", v, v, m) } } return nil } func (action *AssetAction) 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.templates.name == nil { action.templates.name, err = template.New("Name").Parse(action.Name) if err != nil { cl.Errorf("Can't create name template: %v", err) return err } action.templates.description, err = template.New("Description").Parse(action.Description) if err != nil { cl.Errorf("Can't create description template: %v", err) return err } action.templates.ip, err = template.New("Ip").Parse(action.Ip) if err != nil { cl.Errorf("Can't create ip template: %v", err) return err } action.templates.model, err = template.New("Model").Parse(action.Model) if err != nil { cl.Errorf("Can't create model template: %v", err) return err } action.templates.ports, err = template.New("Ports").Parse(action.Ports) if err != nil { cl.Errorf("Can't create ports 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 } } // Actual action for _, event := range *events { // Prepare body interfaceBody, err := action.ToInterface() if err != nil { return err } // Render templates if _, ok := interfaceBody["name"]; ok { interfaceBody["name"], err = renderTemplate(action.templates.name, event) if err != nil { return err } } if _, ok := interfaceBody["description"]; ok { interfaceBody["description"], err = renderTemplate(action.templates.description, event) if err != nil { return err } } if _, ok := interfaceBody["ip"]; ok { interfaceBody["ip"], err = renderTemplate(action.templates.ip, event) if err != nil { return err } } if _, ok := interfaceBody["model"]; ok { interfaceBody["model"], err = renderTemplate(action.templates.model, event) if err != nil { return err } } if _, ok := interfaceBody["ports"]; ok { interfaceBody["ports"], err = renderTemplate(action.templates.ports, event) if err != nil { return err } } // Add type to connect asset with sensor interfaceBody["sensor"] = event.GetString("type") // Send request jsonBody, err := json.Marshal(interfaceBody) if err != nil { cl.Errorf("Can't serialize body: %v", err) return err } cl.Debugf("Sending requset: %v", string(jsonBody)) request, err := http.NewRequest("POST", viper.GetString(config.ConsoleUrlAsset), 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 } // Check result body, err := ioutil.ReadAll(response.Body) if err != nil { cl.Errorf("Can't read response body: %v", err) return err } err = response.Body.Close() if err != nil { cl.Warningf("%v", err) } cl.Debugf("Got response: %v", string(body)) if response.StatusCode != http.StatusCreated { cl.Errorf("Bad status code: %v", response.Status) return fmt.Errorf("bad server response code: %v", response.Status) } } return nil }