package api import ( "strings" "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"` 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 `yaml:"interface" json:"interface"` ListenPorts []uint16 `yaml:"listen_ports" json:"listen_ports"` BPFFilter string `yaml:"bpf_filter" json:"bpf_filter,omitempty"` LocalIPs []string `yaml:"local_ips" json:"local_ips,omitempty"` // Local IPs to monitor (empty = auto-detect, excludes loopback) ExcludeSourceIPs []string `yaml:"exclude_source_ips" json:"exclude_source_ips,omitempty"` // Source IPs or CIDR ranges to exclude (e.g., ["10.0.0.0/8", "192.168.1.1"]) FlowTimeoutSec int `yaml:"flow_timeout_sec" json:"flow_timeout_sec,omitempty"` // Timeout for TLS handshake extraction (default: 30) PacketBufferSize int `yaml:"packet_buffer_size" json:"packet_buffer_size,omitempty"` // Buffer size for packet channel (default: 1000) LogLevel string `yaml:"log_level" json:"log_level,omitempty"` // Log level: debug, info, warn, error (default: info) } // 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:"-"` // Raw packet data including link-layer header Timestamp int64 `json:"timestamp"` // nanoseconds since epoch LinkType int `json:"-"` // Link type (1=Ethernet, 101=Linux SLL, etc.) } // 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"` ConnID string `json:"conn_id,omitempty"` // Unique flow identifier SNI string `json:"tls_sni,omitempty"` // Server Name Indication ALPN string `json:"tls_alpn,omitempty"` // Application-Layer Protocol Negotiation TLSVersion string `json:"tls_version,omitempty"` // Max TLS version supported SynToCHMs *uint32 `json:"syn_to_clienthello_ms,omitempty"` // Time from SYN to ClientHello (ms) } // Fingerprints contains TLS fingerprints for a client flow // Note: JA4Hash is kept for internal use but not serialized to LogRecord // as the JA4 format already includes its own hash portions type Fingerprints struct { JA4 string `json:"ja4"` JA4Hash string `json:"ja4_hash,omitempty"` // Internal use, not serialized to LogRecord 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 // Correlation & Triage ConnID string `json:"conn_id,omitempty"` // Unique flow identifier SensorID string `json:"sensor_id,omitempty"` // Sensor/captor identifier // TLS elements (ClientHello) TLSVersion string `json:"tls_version,omitempty"` // Max TLS version announced by client SNI string `json:"tls_sni,omitempty"` // Server Name Indication ALPN string `json:"tls_alpn,omitempty"` // Application-Layer Protocol Negotiation // Behavioral detection (Timing) SynToCHMs *uint32 `json:"syn_to_clienthello_ms,omitempty"` // Time from SYN to ClientHello (ms) // Fingerprints // Note: ja4_hash is NOT included - the JA4 format already includes its own hash portions JA4 string `json:"ja4"` JA3 string `json:"ja3,omitempty"` JA3Hash string `json:"ja3_hash,omitempty"` // Timestamp in nanoseconds since Unix epoch Timestamp int64 `json:"timestamp"` } // OutputConfig defines configuration for a single log output type OutputConfig struct { Type string `yaml:"type" json:"type"` // unix_socket, stdout, file, etc. Enabled bool `yaml:"enabled" json:"enabled"` // whether this output is active AsyncBuffer int `yaml:"async_buffer" json:"async_buffer"` // queue size for async writes (e.g., 5000) Params map[string]string `yaml:"params" json:"params"` // specific parameters like socket_path, path, etc. } // AppConfig is the complete ja4sentinel configuration type AppConfig struct { Core Config `yaml:"core" json:"core"` Outputs []OutputConfig `yaml:"outputs" 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) } // Reopenable defines the interface for components that support log file rotation. // Implementations must reopen their output files when receiving a SIGHUP signal. // This is used by systemctl reload to switch to new log files after logrotate. type Reopenable interface { Reopen() error } // 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. // Note: JA4Hash is intentionally NOT included in LogRecord as the JA4 format // already includes its own hash portions (the full 38-character JA4 string is sufficient). func NewLogRecord(ch TLSClientHello, fp *Fingerprints) LogRecord { opts := "" if len(ch.TCPMeta.Options) > 0 { opts = strings.Join(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, ConnID: ch.ConnID, SNI: ch.SNI, ALPN: ch.ALPN, TLSVersion: ch.TLSVersion, SynToCHMs: ch.SynToCHMs, Timestamp: time.Now().UnixNano(), } if fp != nil { rec.JA4 = fp.JA4 rec.JA3 = fp.JA3 rec.JA3Hash = fp.JA3Hash } return rec } // Default values and constants const ( DefaultInterface = "eth0" DefaultPort = 443 DefaultBPFFilter = "" DefaultFlowTimeout = 30 // seconds DefaultPacketBuffer = 1000 // packet channel buffer size DefaultLogLevel = "info" ) // 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, LogLevel: DefaultLogLevel, }, Outputs: []OutputConfig{}, } }