package rules import ( "bytes" "crypto/tls" "encoding/json" "errors" "fmt" "io/ioutil" "iwarma.ru/console/correlator/events" "net/http" "strconv" "strings" "text/template" "unicode" "iwarma.ru/console/correlator/config" log "github.com/sirupsen/logrus" "github.com/spf13/viper" ) const ( FirewallActionType = "firewall" ) type ArmaBool bool func (b ArmaBool) ToInterface() interface{} { if b == true { return 1 } else { return 0 } } func (b *ArmaBool) ParseInterface(v interface{}) error { switch value := v.(type) { case int: if value >= 1 { *b = true } else { *b = false } case bool: *b = ArmaBool(value) case nil: return errors.New("no value") default: return fmt.Errorf("can't parse type: %T with value %v", value, value) } return nil } type FirewallAction struct { Enabled ArmaBool Action string Quick ArmaBool Interface string Direction string IPProtocol string Protocol string SourceNet string SourceNot ArmaBool SourcePort string DestinationNet string DestinationNot ArmaBool DestinationPort string Log ArmaBool Description string Gateway string Sequence string // Where to send request url struct { ip string scheme string key string secret string } templates struct { sourceNet *template.Template sourcePort *template.Template destinationNet *template.Template destinationPort *template.Template } client *http.Client } const ( responseSaved = "saved" ) func (action *FirewallAction) GetType() string { return FirewallActionType } func (action FirewallAction) ToInterface() (map[string]interface{}, error) { result := make(map[string]interface{}) result["type"] = FirewallActionType result["enabled"] = action.Enabled.ToInterface() result["action"] = action.Action result["quick"] = action.Quick.ToInterface() result["interface"] = action.Interface result["interface"] = fmt.Sprintf("[%v]", action.Interface) result["description"] = action.Description result["direction"] = action.Direction result["ipprotocol"] = action.IPProtocol result["protocol"] = action.Protocol result["source_net"] = action.SourceNet result["source_not"] = action.SourceNot.ToInterface() result["source_port"] = action.SourcePort result["destination_net"] = action.DestinationNet result["destination_port"] = action.DestinationPort result["destination_not"] = action.DestinationNot.ToInterface() result["log"] = action.Log.ToInterface() result["gateway"] = action.Gateway result["sequence"] = action.Sequence url := make(map[string]interface{}) url["ip"] = action.url.ip url["key"] = action.url.key url["scheme"] = action.url.scheme url["secret"] = action.url.secret result["sensor"] = url return result, nil } func (action *FirewallAction) 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 != FirewallActionType { return fmt.Errorf("bad type, expect %v got %v", ExecActionType, t) } err := action.Enabled.ParseInterface(m["enabled"]) if err != nil { return fmt.Errorf("can't parse \"enabled\" option: %v", err) } action.Action, ok = m["action"].(string) if !ok { return errors.New("no action") } if _, ok = m["quick"]; ok { err = action.Quick.ParseInterface(m["quick"]) if err != nil { return fmt.Errorf("can't parse \"quick\" option: %v", err) } } if value, ok := m["interface"]; ok { switch v := value.(type) { case string: action.Interface = v case []interface{}: arr := make([]string, len(v)) for i := range v { arr[i] = fmt.Sprintf("%v", v[i]) } action.Interface = strings.Join(arr, ",") case []string: action.Interface = strings.Join(v, ",") default: return fmt.Errorf("bad interface type: %T (%v)", v, v) } } else { return errors.New("no interface field") } action.IPProtocol, ok = m["ipprotocol"].(string) if !ok { return errors.New("no IPProtocol") } action.Protocol, ok = m["protocol"].(string) if !ok { return errors.New("no protocol") } action.Direction, ok = m["direction"].(string) if !ok { return errors.New("no direction") } action.SourceNet, ok = m["source_net"].(string) if !ok { return errors.New("no source net") } if _, ok = m["source_not"]; ok { err = action.SourceNot.ParseInterface(m["source_not"]) if err != nil { return fmt.Errorf("can't parse \"source_not\" option: %v", err) } } action.DestinationNet, ok = m["destination_net"].(string) if !ok { return errors.New("no destination net") } if _, ok = m["destination_not"]; ok { err = action.DestinationNot.ParseInterface(m["destination_not"]) if err != nil { return fmt.Errorf("can't parse \"destination_not\" option: %v", err) } } if _, ok = m["log"]; ok { err = action.Log.ParseInterface(m["log"]) if err != nil { return fmt.Errorf("can't parse \"log\" option: %v", err) } } if _, ok = m["sequence"]; ok { switch v := m["sequence"].(type) { case string: action.Sequence = v case int: action.Sequence = fmt.Sprintf("%v", v) default: return fmt.Errorf("can't parse type %T with value of %v", m["sequence"], m["sequence"]) } } if urlInterface, ok := m["sensor"]; ok { url, ok := urlInterface.(map[string]interface{}) if !ok { return errors.New("can't convert sensor value") } action.url.ip, ok = url["ip"].(string) if !ok { return errors.New("no Firewall target IP set") } action.url.key, ok = url["key"].(string) if !ok { return errors.New("no Firewall target key set") } action.url.scheme, ok = url["scheme"].(string) if !ok { return errors.New("no Firewall target scheme set") } action.url.secret, ok = url["secret"].(string) if !ok { return errors.New("no Firewall target secret set") } } else { return errors.New("no sensor field") } // Not required action.Description, _ = m["description"].(string) action.SourcePort, _ = m["source_port"].(string) action.DestinationPort, _ = m["destination_port"].(string) action.Gateway, _ = m["gateway"].(string) return nil } // Create rule structure for ARMAIF func (action FirewallAction) toRule() (map[string]interface{}, map[string]interface{}, error) { res := make(map[string]interface{}) var err error res["enabled"] = action.Enabled.ToInterface() res["sequence"], err = strconv.Atoi(action.Sequence) if err != nil { return nil, nil, err } res["action"] = action.Action res["quick"] = action.Quick.ToInterface() res["interface"] = action.Interface res["direction"] = action.Direction res["ipprotocol"] = action.IPProtocol res["protocol"] = action.Protocol res["source_net"] = action.SourceNet res["source_port"] = action.SourcePort res["source_not"] = action.SourceNot.ToInterface() res["destination_net"] = action.DestinationNet res["destination_port"] = action.DestinationPort res["destination_not"] = action.DestinationNot.ToInterface() res["gateway"] = action.Gateway res["log"] = action.Log.ToInterface() res["description"] = action.Description rule := make(map[string]interface{}) rule["rule"] = res return rule, res, nil } func (action *FirewallAction) ApplyRule() error { cl := log.WithFields(log.Fields{"type": FirewallActionType, "part": "ApplyRule"}) request, err := http.NewRequest("POST", fmt.Sprintf("%v://%v/api/firewall/filter/apply/", action.url.scheme, action.url.ip), nil) if err != nil { cl.Errorf("Can't create request: %v", err) return err } // Set headers request.SetBasicAuth(action.url.key, action.url.secret) // 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) { // No need to wait go DumpNetwork("firewall", *request.URL, nil, body, response.StatusCode) } // Check status code if response.StatusCode != http.StatusOK { cl.Errorf("Bad status code: %v", response.Status) return fmt.Errorf("bad server response code: %v", response.Status) } // Check response var resp map[string]interface{} err = json.Unmarshal(body, &resp) if err != nil { cl.Errorf("Can't unmarshall response: %v", err) return fmt.Errorf("can't unmarshall response: %v", err) } statusInt, ok := resp["status"] if !ok { cl.Errorf("No status field: %v", resp) return fmt.Errorf("no status field: %v", resp) } status, ok := statusInt.(string) if !ok { cl.Error("Can't convert status to string") return errors.New("can't convert status to string") } // Trim all \n and so on status = strings.TrimFunc(status, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) }) if status == "OK" { return nil } cl.Errorf("Bad status value: %v", status) return fmt.Errorf("bad status value: %v", status) } func (action *FirewallAction) Perform(events *[]*events.Event) error { cl := log.WithFields(log.Fields{"type": FirewallActionType, "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.sourceNet == nil { action.templates.sourceNet, err = template.New("SourceNet").Parse(action.SourceNet) if err != nil { cl.Errorf("Can't create source net template: %v", err) return err } action.templates.sourcePort, err = template.New("SourcePort").Parse(action.SourcePort) if err != nil { cl.Errorf("Can't create source port template: %v", err) return err } action.templates.destinationNet, err = template.New("DestinationNet").Parse(action.DestinationNet) if err != nil { cl.Errorf("Can't create destination net template: %v", err) return err } action.templates.destinationPort, err = template.New("DestinationPort").Parse(action.DestinationPort) if err != nil { cl.Errorf("Can't create destination port template: %v", err) return err } } // Actual action for _, event := range *events { // Prepare body ibody, data, err := action.toRule() if err != nil { return err } // Render templates if _, ok := data["source_net"]; ok { data["source_net"], err = renderTemplate(action.templates.sourceNet, event) if err != nil { return err } } if _, ok := data["source_port"]; ok { data["source_port"], err = renderTemplate(action.templates.sourcePort, event) if err != nil { return err } } if _, ok := data["destination_net"]; ok { data["destination_net"], err = renderTemplate(action.templates.destinationNet, event) if err != nil { return err } } if _, ok := data["destination_port"]; ok { data["destination_port"], err = renderTemplate(action.templates.destinationPort, event) if err != nil { return err } } // Check if we have a http client if action.client == nil { if viper.GetBool(config.ActionFirewallRuleIgnoreSSLErrors) { transport := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } action.client = &http.Client{Transport: transport} cl.Debug("Disable SSL validation") } else { action.client = &http.Client{} } } // Prepare body jsonBody, err := json.Marshal(ibody) if err != nil { cl.Errorf("Can't marshall request body: %v", err) return err } request, err := http.NewRequest("POST", fmt.Sprintf("%v://%v/api/firewall/filter/addRule/", action.url.scheme, action.url.ip), bytes.NewBuffer(jsonBody)) if err != nil { cl.Errorf("Can't create request: %v", err) return err } // Set headers request.SetBasicAuth(action.url.key, action.url.secret) 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) { // No need to wait go DumpNetwork("firewall", *request.URL, ibody, body, response.StatusCode) } // Check status code if response.StatusCode != http.StatusOK { cl.Errorf("Bad status code: %v", response.Status) return fmt.Errorf("bad server response code: %v", response.Status) } // Check response var resp map[string]interface{} err = json.Unmarshal(body, &resp) if err != nil { cl.Errorf("Can't unmarshall response: %v", err) return fmt.Errorf("can't unmarshall response: %v", err) } res, ok := resp["result"] if !ok { cl.Errorf("Bad ARMAIF response: %v", string(body)) return fmt.Errorf("bad ARMAIF response: %v", string(body)) } resVal, ok := res.(string) if !ok { cl.Errorf("Bad ARMAIF response type: %T - %v", resVal, resVal) return fmt.Errorf("bad ARMAIF response type: %T - %v", resVal, resVal) } if resVal != responseSaved { cl.Errorf(`Bad armaif response. Expect "%v", got ""%v"`, responseSaved, resVal) cl.Debugf(`Got response: %v`, string(body)) return fmt.Errorf("bad armaif response. Expect \"%v\", got \"%v\"", responseSaved, resVal) } } return action.ApplyRule() } func (action FirewallAction) MarshalJSON() ([]byte, error) { data, err := action.ToInterface() if err != nil { return nil, err } return json.Marshal(data) } func (action *FirewallAction) UnmarshalJSON(b []byte) error { var data interface{} err := json.Unmarshal(b, &data) if err != nil { return err } return action.ParseInterface(data) }