diff --git a/internal/api/types.go b/internal/api/types.go new file mode 100644 index 0000000..7a14391 --- /dev/null +++ b/internal/api/types.go @@ -0,0 +1,209 @@ +package api + +import ( + "time" +) + +// ServiceLog represents internal service logging for diagnostics +type ServiceLog struct { + Level string `json:"level"` + Component string `json:"component"` + Message string `json:"message"` + Details map[string]string `json:"details,omitempty"` +} + +// Config holds basic network and TLS configuration +type Config struct { + Interface string `json:"interface"` + ListenPorts []uint16 `json:"listen_ports"` + BPFFilter string `json:"bpf_filter,omitempty"` +} + +// IPMeta contains IP metadata for stack fingerprinting +type IPMeta struct { + TTL uint8 `json:"ttl"` + TotalLength uint16 `json:"total_length"` + IPID uint16 `json:"id"` + DF bool `json:"df"` +} + +// TCPMeta contains TCP metadata for stack fingerprinting +type TCPMeta struct { + WindowSize uint16 `json:"window_size"` + MSS uint16 `json:"mss,omitempty"` + WindowScale uint8 `json:"window_scale,omitempty"` + Options []string `json:"options"` +} + +// RawPacket represents a raw packet captured from the network +type RawPacket struct { + Data []byte `json:"-"` // Not serialized + Timestamp int64 `json:"timestamp"` // nanoseconds since epoch +} + +// TLSClientHello represents a client-side TLS ClientHello with IP/TCP metadata +type TLSClientHello struct { + SrcIP string `json:"src_ip"` + SrcPort uint16 `json:"src_port"` + DstIP string `json:"dst_ip"` + DstPort uint16 `json:"dst_port"` + Payload []byte `json:"-"` // Not serialized + IPMeta IPMeta `json:"ip_meta"` + TCPMeta TCPMeta `json:"tcp_meta"` +} + +// Fingerprints contains TLS fingerprints for a client flow +type Fingerprints struct { + JA4 string `json:"ja4"` + JA4Hash string `json:"ja4_hash"` + JA3 string `json:"ja3,omitempty"` + JA3Hash string `json:"ja3_hash,omitempty"` +} + +// LogRecord is the final log record, serialized as a flat JSON object +type LogRecord struct { + SrcIP string `json:"src_ip"` + SrcPort uint16 `json:"src_port"` + DstIP string `json:"dst_ip"` + DstPort uint16 `json:"dst_port"` + + // Flattened IPMeta fields + IPTTL uint8 `json:"ip_meta_ttl"` + IPTotalLen uint16 `json:"ip_meta_total_length"` + IPID uint16 `json:"ip_meta_id"` + IPDF bool `json:"ip_meta_df"` + + // Flattened TCPMeta fields + TCPWindow uint16 `json:"tcp_meta_window_size"` + TCPMSS uint16 `json:"tcp_meta_mss,omitempty"` + TCPWScale uint8 `json:"tcp_meta_window_scale,omitempty"` + TCPOptions string `json:"tcp_meta_options"` // comma-separated list + + // Fingerprints + JA4 string `json:"ja4"` + JA4Hash string `json:"ja4_hash"` + JA3 string `json:"ja3,omitempty"` + JA3Hash string `json:"ja3_hash,omitempty"` +} + +// OutputConfig defines configuration for a single log output +type OutputConfig struct { + Type string `json:"type"` // unix_socket, stdout, file, etc. + Enabled bool `json:"enabled"` // whether this output is active + Params map[string]string `json:"params"` // specific parameters like socket_path, path, etc. +} + +// AppConfig is the complete ja4sentinel configuration +type AppConfig struct { + Core Config `json:"core"` + Outputs []OutputConfig `json:"outputs"` +} + +// Loader interface loads configuration from file/env/CLI +type Loader interface { + Load() (AppConfig, error) +} + +// Capture interface provides raw network packets +type Capture interface { + Run(cfg Config, out chan<- RawPacket) error +} + +// Parser converts RawPacket to TLSClientHello +type Parser interface { + Process(pkt RawPacket) (*TLSClientHello, error) +} + +// Engine generates JA4 fingerprints from TLS ClientHello +type Engine interface { + FromClientHello(ch TLSClientHello) (*Fingerprints, error) +} + +// Writer is the generic interface for writing results +type Writer interface { + Write(rec LogRecord) error +} + +// UnixSocketWriter implements Writer sending logs to a UNIX socket +type UnixSocketWriter interface { + Writer + Close() error +} + +// MultiWriter combines multiple Writers +type MultiWriter interface { + Writer + Add(writer Writer) + CloseAll() error +} + +// Builder constructs Writers from AppConfig +type Builder interface { + NewFromConfig(cfg AppConfig) (Writer, error) +} + +// Helper functions for creating and converting records + +// NewLogRecord creates a LogRecord from TLSClientHello and Fingerprints +func NewLogRecord(ch TLSClientHello, fp *Fingerprints) LogRecord { + opts := "" + if len(ch.TCPMeta.Options) > 0 { + opts = joinStringSlice(ch.TCPMeta.Options, ",") + } + + rec := LogRecord{ + SrcIP: ch.SrcIP, + SrcPort: ch.SrcPort, + DstIP: ch.DstIP, + DstPort: ch.DstPort, + IPTTL: ch.IPMeta.TTL, + IPTotalLen: ch.IPMeta.TotalLength, + IPID: ch.IPMeta.IPID, + IPDF: ch.IPMeta.DF, + TCPWindow: ch.TCPMeta.WindowSize, + TCPMSS: ch.TCPMeta.MSS, + TCPWScale: ch.TCPMeta.WindowScale, + TCPOptions: opts, + } + + if fp != nil { + rec.JA4 = fp.JA4 + rec.JA4Hash = fp.JA4Hash + rec.JA3 = fp.JA3 + rec.JA3Hash = fp.JA3Hash + } + + return rec +} + +// Helper to join string slice with separator +func joinStringSlice(slice []string, sep string) string { + if len(slice) == 0 { + return "" + } + result := slice[0] + for _, s := range slice[1:] { + result += sep + s + } + return result +} + +// Default values and constants + +const ( + DefaultInterface = "eth0" + DefaultPort = 443 + DefaultBPFFilter = "" +) + +// DefaultConfig returns a configuration with sensible defaults +func DefaultConfig() AppConfig { + return AppConfig{ + Core: Config{ + Interface: DefaultInterface, + ListenPorts: []uint16{DefaultPort}, + BPFFilter: DefaultBPFFilter, + }, + Outputs: []OutputConfig{}, + } +}