old_console/correlator/rules/action_firewall.go
2024-11-02 14:12:45 +03:00

568 lines
14 KiB
Go

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)
}