package api // 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"` Timestamp int64 `json:"timestamp,omitempty"` // Unix nanoseconds (auto-set by logger) TraceID string `json:"trace_id,omitempty"` // Optional distributed tracing ID ConnID string `json:"conn_id,omitempty"` // Optional TCP flow identifier } // 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"` FlowTimeoutSec int `json:"flow_timeout_sec,omitempty"` // Timeout for TLS handshake extraction (default: 30) PacketBufferSize int `json:"packet_buffer_size,omitempty"` // Buffer size for packet channel (default: 1000) } // 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 defines the interface for loading application configuration. // Implementations must read configuration from a YAML file, merge with // environment variables (JA4SENTINEL_*), and validate the final result. type Loader interface { Load() (AppConfig, error) } // Capture defines the interface for capturing raw network packets. // Implementations must listen on a configured network interface, apply // BPF filters for specified ports, and emit RawPacket objects to a channel. // The Close method must be called to release resources (e.g., pcap handle). type Capture interface { Run(cfg Config, out chan<- RawPacket) error Close() error } // Parser defines the interface for extracting TLS ClientHello messages // from raw network packets. Implementations must track TCP connection states, // reassemble fragmented handshakes, and return TLSClientHello objects with // IP/TCP metadata. Returns nil for non-TLS or non-ClientHello packets. type Parser interface { Process(pkt RawPacket) (*TLSClientHello, error) Close() error } // Engine defines the interface for generating TLS fingerprints. // Implementations must analyze TLS ClientHello payloads and produce // JA4 (required) and optionally JA3 fingerprint strings. type Engine interface { FromClientHello(ch TLSClientHello) (*Fingerprints, error) } // Writer defines the generic interface for writing log records. // Implementations must serialize LogRecord objects and send them to // a destination (stdout, file, UNIX socket, etc.). type Writer interface { Write(rec LogRecord) error } // UnixSocketWriter extends Writer with a Close method for UNIX socket cleanup. // Implementations must connect to a UNIX socket at the specified path and // write JSON-encoded LogRecord objects. Reconnection logic should be // implemented for transient socket failures. type UnixSocketWriter interface { Writer Close() error } // MultiWriter extends Writer to support multiple output destinations. // Implementations must write each LogRecord to all registered writers // and provide methods to add writers and close all connections. type MultiWriter interface { Writer Add(writer Writer) CloseAll() error } // Builder defines the interface for constructing output writers from configuration. // Implementations must parse AppConfig.Outputs and create appropriate Writer // instances (StdoutWriter, FileWriter, UnixSocketWriter), combining them // into a MultiWriter if multiple outputs are configured. type Builder interface { NewFromConfig(cfg AppConfig) (Writer, error) } // Logger defines the interface for structured service logging. // Implementations must emit JSON-formatted log entries to stdout/stderr // with support for multiple log levels (DEBUG, INFO, WARN, ERROR). // Each log entry includes timestamp, level, component, message, and optional details. type Logger interface { Debug(component, message string, details map[string]string) Info(component, message string, details map[string]string) Warn(component, message string, details map[string]string) Error(component, message string, details map[string]string) } // Helper functions for creating and converting records // NewLogRecord creates a flattened LogRecord from TLSClientHello and Fingerprints. // Converts TCPMeta options to a comma-separated string and creates pointer values // for optional fields (MSS, WindowScale) to support proper JSON omitempty behavior. // If fingerprints is nil, the JA4/JA3 fields will be empty strings. func NewLogRecord(ch TLSClientHello, fp *Fingerprints) LogRecord { opts := "" if len(ch.TCPMeta.Options) > 0 { opts = joinStringSlice(ch.TCPMeta.Options, ",") } // Helper to create pointer from value for optional fields var mssPtr *uint16 if ch.TCPMeta.MSS != 0 { mssPtr = &ch.TCPMeta.MSS } var wScalePtr *uint8 if ch.TCPMeta.WindowScale != 0 { wScalePtr = &ch.TCPMeta.WindowScale } 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: mssPtr, TCPWScale: wScalePtr, 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 = "" DefaultFlowTimeout = 30 // seconds DefaultPacketBuffer = 1000 // packet channel buffer size // Logging levels LogLevelDebug = "DEBUG" LogLevelInfo = "INFO" LogLevelWarn = "WARN" LogLevelError = "ERROR" ) // DefaultConfig returns an AppConfig with sensible default values. // Uses eth0 as the default interface, port 443 for monitoring, // no BPF filter, a 30-second flow timeout, and a 1000-packet // channel buffer. Returns an empty outputs slice (caller must // configure outputs explicitly). func DefaultConfig() AppConfig { return AppConfig{ Core: Config{ Interface: DefaultInterface, ListenPorts: []uint16{DefaultPort}, BPFFilter: DefaultBPFFilter, FlowTimeoutSec: DefaultFlowTimeout, PacketBufferSize: DefaultPacketBuffer, }, Outputs: []OutputConfig{}, } }