pkg/generator/aws/firewall/firewall.go (193 lines of code) (raw):
// Package firewall generates AWS Network Firewall log messages. It currently supports generating
// netflow and alert events, and supports generating detailed TCP netflow and HTTP alert logs.
//
// Configuration:
//
// event_type: Specify the type of event to generate, or leave blank for random.
// Valid values are: alert, netflow.
//
// - generator:
// type: aws:firewall
// event_type: netflow
package firewall
import (
"encoding/json"
"fmt"
"math/rand"
"net"
"strconv"
"time"
"github.com/elastic/go-ucfg"
"github.com/elastic/spigot/pkg/generator"
"github.com/elastic/spigot/pkg/random"
)
// Name is the name used in the configuration file and the registry.
const Name = "aws:firewall"
const timestampFmt = "2006-01-02T15:04:05.999999-0700"
const (
EventTypeAlert = "alert"
EventTypeNetflow = "netflow"
ProtocolICMP = "ICMP"
ProtocolTCP = "TCP"
ProtocolUDP = "UDP"
AppProtoNone = ""
AppProtoHTTP = "http"
AlertActionAllowed = "allowed"
AlertActionBlocked = "blocked"
)
var (
eventTypes = [...]string{EventTypeAlert, EventTypeNetflow}
protocols = [...]string{ProtocolICMP, ProtocolTCP, ProtocolUDP}
tcpAppProtos = [...]string{AppProtoNone, AppProtoHTTP}
alertActions = [...]string{AlertActionAllowed, AlertActionBlocked}
)
// HTTPData provides fields for HTTP records.
type HTTPData struct {
Hostname string `json:"hostname"`
URL string `json:"URL"`
HTTPUserAgent string `json:"http_user_agent"`
HTTPMethod string `json:"http_method"`
Protocol string `json:"protocol"`
Length int `json:"length"`
}
// AlertData provides fields for alert event records.
type AlertData struct {
Action string `json:"action"`
SignatureID int `json:"signature_id"`
Rev int `json:"rev"`
Signature string `json:"signature"`
Category string `json:"category"`
Severity int `json:"severity"`
}
// NetflowData provides fields for netflow event records
type NetflowData struct {
Pkts int `json:"pkts"`
Bytes int `json:"bytes"`
Start string `json:"start"`
End string `json:"end"`
Age int `json:"age"`
MinTTL int `json:"min_ttl"`
MaxTTL int `json:"max_ttl"`
}
// TCPData provides fields for TCP-based records.
type TCPData struct {
TCPFlags string `json:"tcp_flags"`
Fin bool `json:"fin,omitempty"`
Syn bool `json:"syn,omitempty"`
Rst bool `json:"rst,omitempty"`
Psh bool `json:"psh,omitempty"`
Ack bool `json:"ack,omitempty"`
Urg bool `json:"urg,omitempty"`
}
type EventData struct {
Timestamp string `json:"timestamp"`
FlowID int `json:"flow_id"`
EventType string `json:"event_type"`
SrcIP net.IP `json:"src_ip"`
SrcPort int `json:"src_port"`
DstIP net.IP `json:"dst_ip"`
DstPort int `json:"dst_port"`
Proto string `json:"proto"`
AppProto string `json:"app_proto,omitempty"`
Alert *AlertData `json:"alert,omitempty"`
Netflow *NetflowData `json:"netflow,omitempty"`
HTTP *HTTPData `json:"http,omitempty"`
TCP *TCPData `json:"tcp,omitempty"`
}
// Firewall holds the random fields for a firewall record.
type Firewall struct {
FirewallName string `json:"firewall_name"`
AvailabilityZone string `json:"availability_zone"`
Event EventData `json:"event"`
EventTimestamp string `json:"event_timestamp"`
}
// Generator provides an AWS Firewall record generator.
type Generator struct {
Data Firewall
eventType string
}
func init() {
_ = generator.Register(Name, New)
}
// New is the factory for AWS Firewall objects.
func New(cfg *ucfg.Config) (generator.Generator, error) {
c := defaultConfig()
if err := cfg.Unpack(&c); err != nil {
return nil, err
}
g := Generator{
eventType: c.EventType,
}
return &g, nil
}
// Next produces the next AWS Firewall record.
func (g *Generator) Next() ([]byte, error) {
var err error
g.randomize()
data, err := json.Marshal(&g.Data)
if err != nil {
return nil, fmt.Errorf("unable to marshal %s data: %w", Name, err)
}
return data, nil
}
func (g *Generator) randomize() {
now := time.Now()
g.Data = Firewall{
FirewallName: fmt.Sprintf("Firewall-%d", rand.Intn(100)),
AvailabilityZone: random.AWSAvailabilityZone(),
EventTimestamp: strconv.Itoa(int(now.Unix())),
Event: EventData{
Timestamp: now.Format(timestampFmt),
FlowID: rand.Int(),
SrcIP: random.IPv4(),
SrcPort: random.Port(),
DstIP: random.IPv4(),
DstPort: random.Port(),
Proto: protocols[rand.Intn(len(protocols))],
},
}
if g.eventType == "" {
g.Data.Event.EventType = eventTypes[rand.Intn(len(eventTypes))]
} else {
g.Data.Event.EventType = g.eventType
}
switch g.Data.Event.EventType {
case EventTypeAlert:
g.randomizeAlert()
case EventTypeNetflow:
g.randomizeNetflow(now)
}
}
func (g *Generator) randomizeAlert() {
signature := rand.Intn(1024)
g.Data.Event.Alert = &AlertData{
Action: alertActions[rand.Intn(len(alertActions))],
SignatureID: signature,
Rev: rand.Intn(1024),
Signature: fmt.Sprintf("Signature-%d", signature),
Category: fmt.Sprintf("Category-%d", rand.Intn(100)),
Severity: rand.Intn(6),
}
if g.Data.Event.Proto == ProtocolTCP {
g.randomizeTCP()
}
}
func (g *Generator) randomizeNetflow(now time.Time) {
ttl := rand.Intn(256)
start := now.Add(-time.Duration(rand.Intn(60)) * time.Minute)
g.Data.Event.Netflow = &NetflowData{
Pkts: rand.Intn(100),
Start: start.Format(timestampFmt),
End: now.Format(timestampFmt),
Age: int(now.Sub(start).Seconds()),
MinTTL: ttl,
MaxTTL: ttl,
}
g.Data.Event.Netflow.Bytes = g.Data.Event.Netflow.Pkts*rand.Intn(1024) + 1
}
func (g *Generator) randomizeTCP() {
g.Data.Event.AppProto = tcpAppProtos[rand.Intn(len(tcpAppProtos))]
flags := rand.Intn(64)
g.Data.Event.TCP = &TCPData{
TCPFlags: fmt.Sprintf("%02d", flags),
Fin: flags&(1<<0) != 0,
Syn: flags&(1<<1) != 0,
Rst: flags&(1<<2) != 0,
Psh: flags&(1<<3) != 0,
Ack: flags&(1<<4) != 0,
Urg: flags&(1<<5) != 0,
}
if g.Data.Event.AppProto == AppProtoHTTP {
g.randomizeHTTP()
}
}
func (g *Generator) randomizeHTTP() {
g.Data.Event.HTTP = &HTTPData{
Hostname: fmt.Sprintf("HTTPHost-%d", rand.Intn(100)),
URL: fmt.Sprintf("/random-%d.html", rand.Intn(100)),
HTTPUserAgent: random.UserAgent(),
HTTPMethod: random.HTTPMethod(),
Protocol: random.HTTPVersion(),
Length: rand.Intn(1024),
}
}