185 lines
4.8 KiB
Go
185 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func main() {
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if len(strings.TrimSpace(line)) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Unmarshal the JSON event
|
|
var event map[string]interface{}
|
|
if err := json.Unmarshal([]byte(line), &event); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing JSON: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
// Transform the event into a flat CIM structure.
|
|
cimEvent := transformToCIM(event)
|
|
|
|
// Marshal the new event and output it
|
|
out, err := json.Marshal(cimEvent)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error marshaling CIM event: %v\n", err)
|
|
continue
|
|
}
|
|
fmt.Println(string(out))
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// transformToCIM maps the nested pfSense log structure to a flat CIM structure.
|
|
func transformToCIM(event map[string]interface{}) map[string]interface{} {
|
|
cim := make(map[string]interface{})
|
|
|
|
// Timestamp: preserve it as is.
|
|
if ts, ok := event["timestamp"]; ok {
|
|
cim["timestamp"] = ts
|
|
}
|
|
|
|
// Optionally copy log_level if available.
|
|
if lvl, ok := event["log_level"]; ok {
|
|
cim["log_level"] = lvl
|
|
}
|
|
|
|
// Our pfSense log is expected to have a "log" object.
|
|
logObj, ok := event["log"].(map[string]interface{})
|
|
if !ok {
|
|
// If not, return the original event.
|
|
return event
|
|
}
|
|
|
|
// Extract common fields from "base"
|
|
base, _ := logObj["base"].(map[string]interface{})
|
|
if base != nil {
|
|
if act, ok := base["action"].(string); ok && act != "" {
|
|
cim["action"] = strings.ToLower(act)
|
|
}
|
|
if dir, ok := base["direction"].(string); ok && dir != "" {
|
|
cim["direction"] = strings.ToLower(dir)
|
|
}
|
|
if rn, ok := base["rule_number"]; ok {
|
|
// try converting rule_number to integer if possible.
|
|
if rnStr, ok := rn.(string); ok {
|
|
if rnInt, err := strconv.Atoi(rnStr); err == nil {
|
|
cim["rule_number"] = rnInt
|
|
} else {
|
|
cim["rule_number"] = rnStr
|
|
}
|
|
} else {
|
|
cim["rule_number"] = rn
|
|
}
|
|
}
|
|
if reason, ok := base["reason"].(string); ok && reason != "" {
|
|
cim["reason"] = reason
|
|
}
|
|
}
|
|
|
|
// Extract IP data: source, destination, and packet length
|
|
ipData, _ := logObj["ip_data"].(map[string]interface{})
|
|
if ipData != nil {
|
|
if src, ok := ipData["source_address"].(string); ok && src != "" {
|
|
cim["src"] = src
|
|
}
|
|
if dest, ok := ipData["destination_address"].(string); ok && dest != "" {
|
|
cim["dest"] = dest
|
|
}
|
|
if length, ok := ipData["length"].(string); ok && length != "" {
|
|
if l, err := strconv.Atoi(length); err == nil {
|
|
cim["packet_length"] = l
|
|
} else {
|
|
cim["packet_length"] = length
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine transport protocol from ip_specific_data.
|
|
ipSpec, _ := logObj["ip_specific_data"].(map[string]interface{})
|
|
var protocol string
|
|
if ipSpec != nil {
|
|
if ipVersion, ok := base["ip_version"].(string); ok {
|
|
if ipVersion == "4" {
|
|
if ipv4, ok := ipSpec["ipv4_header"].(map[string]interface{}); ok {
|
|
if pt, ok := ipv4["protocol_text"].(string); ok {
|
|
protocol = strings.ToLower(pt)
|
|
}
|
|
}
|
|
} else if ipVersion == "6" {
|
|
if ipv6, ok := ipSpec["ipv6_header"].(map[string]interface{}); ok {
|
|
if pt, ok := ipv6["protocol_text"].(string); ok {
|
|
protocol = strings.ToLower(pt)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if protocol != "" {
|
|
cim["transport"] = protocol
|
|
}
|
|
|
|
// Map protocol-specific data for TCP and UDP.
|
|
protoSpec, _ := logObj["protocol_specific_data"].(map[string]interface{})
|
|
if protoSpec != nil {
|
|
// For TCP/UDP, map ports and data length.
|
|
if protocol == "tcp" || protocol == "udp" {
|
|
if sp, ok := protoSpec["source_port"].(string); ok && sp != "" {
|
|
if port, err := strconv.Atoi(sp); err == nil {
|
|
cim["src_port"] = port
|
|
} else {
|
|
cim["src_port"] = sp
|
|
}
|
|
}
|
|
if dp, ok := protoSpec["destination_port"].(string); ok && dp != "" {
|
|
if port, err := strconv.Atoi(dp); err == nil {
|
|
cim["dest_port"] = port
|
|
} else {
|
|
cim["dest_port"] = dp
|
|
}
|
|
}
|
|
if dl, ok := protoSpec["data_length"].(string); ok && dl != "" {
|
|
if d, err := strconv.Atoi(dl); err == nil {
|
|
// Map to bytes_in or bytes_out based on direction.
|
|
if dstr, ok := cim["direction"].(string); ok {
|
|
if dstr == "in" {
|
|
cim["bytes_in"] = d
|
|
} else if dstr == "out" {
|
|
cim["bytes_out"] = d
|
|
} else {
|
|
cim["data_length"] = d
|
|
}
|
|
} else {
|
|
cim["data_length"] = d
|
|
}
|
|
} else {
|
|
cim["data_length"] = dl
|
|
}
|
|
}
|
|
} else {
|
|
// For non-TCP/UDP protocols, if ports exist in the raw data, include them.
|
|
if sp, ok := protoSpec["source_port"].(string); ok && sp != "" {
|
|
cim["src_port"] = sp
|
|
}
|
|
if dp, ok := protoSpec["destination_port"].(string); ok && dp != "" {
|
|
cim["dest_port"] = dp
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the new flat event with only CIM fields.
|
|
return cim
|
|
}
|