From 0b67c1a429b7c155da50ef7056737c521cfb3c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20I=C3=B1iguez=20Goia?= Date: Sat, 11 May 2024 18:23:20 +0200 Subject: [PATCH] more work on reloading configuration continuation of previous commit bde5d34deb5e5c5858991510c48fbd58913a193a - Allow to reconfigure stats limits (how many events we keep on the daemon, number of workers, ...) - Allow to reconfigure loggers. --- daemon/statistics/stats.go | 22 +++++++++++--- daemon/ui/auth/auth.go | 4 --- daemon/ui/client.go | 29 +++++++++---------- daemon/ui/config/config.go | 38 +++++++++++++----------- daemon/ui/config_utils.go | 59 +++++++++++++++++++++----------------- 5 files changed, 85 insertions(+), 67 deletions(-) diff --git a/daemon/statistics/stats.go b/daemon/statistics/stats.go index b35f1a57..e9832d43 100644 --- a/daemon/statistics/stats.go +++ b/daemon/statistics/stats.go @@ -1,6 +1,7 @@ package statistics import ( + "context" "strconv" "sync" "time" @@ -29,6 +30,8 @@ type conEvent struct { // Statistics holds the connections and statistics the daemon intercepts. // The connections are stored in the Events slice. type Statistics struct { + ctx context.Context + cancel context.CancelFunc Started time.Time logger *loggers.LoggerManager rules *rule.Loader @@ -59,7 +62,10 @@ type Statistics struct { // New returns a new Statistics object and initializes the go routines to update the stats. func New(rules *rule.Loader) (stats *Statistics) { + ctx, cancel := context.WithCancel(context.Background()) stats = &Statistics{ + ctx: ctx, + cancel: cancel, Started: time.Now(), Events: make([]*Event, 0), ByProto: make(map[string]uint64), @@ -79,14 +85,18 @@ func New(rules *rule.Loader) (stats *Statistics) { } // SetLoggers sets the configured loggers where we'll write the events. -func (s *Statistics) SetLoggers(loggers *loggers.LoggerManager) { - s.logger = loggers +func (s *Statistics) SetLoggers(loggermgr *loggers.LoggerManager) { + s.Lock() + s.logger = loggermgr + s.Unlock() } // SetLimits configures the max events to keep in the backlog before sending // the stats to the UI, or while the UI is not connected. // if the backlog is full, it'll be shifted by one. func (s *Statistics) SetLimits(config StatsConfig) { + s.cancel() + s.ctx, s.cancel = context.WithCancel(context.Background()) if config.MaxEvents > 0 { s.maxEvents = config.MaxEvents } @@ -99,7 +109,7 @@ func (s *Statistics) SetLimits(config StatsConfig) { } log.Info("Stats, max events: %d, max stats: %d, max workers: %d", s.maxStats, s.maxEvents, s.maxWorkers) for i := 0; i < s.maxWorkers; i++ { - go s.eventWorker(i) + go s.eventWorker(i, s.ctx.Done()) } } @@ -164,15 +174,19 @@ func (s *Statistics) incMap(m *map[string]uint64, key string) { } } -func (s *Statistics) eventWorker(id int) { +func (s *Statistics) eventWorker(id int, done <-chan struct{}) { log.Debug("Stats worker #%d started.", id) for true { select { + case <-done: + goto Exit case job := <-s.jobs: s.onConnection(job.con, job.match, job.wasMissed) } } +Exit: + log.Debug("stats.worker() %d exited", id) } func (s *Statistics) onConnection(con *conman.Connection, match *rule.Rule, wasMissed bool) { diff --git a/daemon/ui/auth/auth.go b/daemon/ui/auth/auth.go index b02a7e8d..cf26dc21 100644 --- a/daemon/ui/auth/auth.go +++ b/daemon/ui/auth/auth.go @@ -39,13 +39,9 @@ const ( // New returns the configuration that the UI will use // to connect with the server. func New(config *config.Config) (grpc.DialOption, error) { - config.RLock() - credsType := config.Server.Authentication.Type tlsOpts := config.Server.Authentication.TLSOptions - config.RUnlock() - if credsType == "" || credsType == AuthSimple { log.Debug("UI auth: simple") return grpc.WithInsecure(), nil diff --git a/daemon/ui/client.go b/daemon/ui/client.go index f65a0c8e..e1ae6b05 100644 --- a/daemon/ui/client.go +++ b/daemon/ui/client.go @@ -31,7 +31,6 @@ var ( // While the GUI is connected, deny by default everything until the user takes an action. clientConnectedRule = rule.Create("ui.client.connected", "", true, false, false, rule.Deny, rule.Once, dummyOperator) clientErrorRule = rule.Create("ui.client.error", "", true, false, false, rule.Allow, rule.Once, dummyOperator) - clientConfig config.Config maxQueuedAlerts = 1024 ) @@ -42,6 +41,7 @@ type Client struct { streamNotifications protocol.UI_NotificationsClient clientCtx context.Context clientCancel context.CancelFunc + config config.Config loggers *loggers.LoggerManager stats *statistics.Statistics @@ -88,9 +88,8 @@ func NewClient(socketPath, localConfigFile string, stats *statistics.Statistics, if socketPath != "" { c.setSocketPath(c.getSocketPath(socketPath)) } - procmon.EventsCache.SetComputeChecksums(clientConfig.Rules.EnableChecksums) - rules.EnableChecksums(clientConfig.Rules.EnableChecksums) - stats.SetLimits(clientConfig.Stats) + procmon.EventsCache.SetComputeChecksums(c.config.Rules.EnableChecksums) + rules.EnableChecksums(c.config.Rules.EnableChecksums) return c } @@ -108,26 +107,26 @@ func (c *Client) Close() { // ProcMonitorMethod returns the monitor method configured. // If it's not present in the config file, it'll return an empty string. func (c *Client) ProcMonitorMethod() string { - clientConfig.RLock() - defer clientConfig.RUnlock() - return clientConfig.ProcMonitorMethod + c.RLock() + defer c.RUnlock() + return c.config.ProcMonitorMethod } // InterceptUnknown returns func (c *Client) InterceptUnknown() bool { - clientConfig.RLock() - defer clientConfig.RUnlock() - return clientConfig.InterceptUnknown + c.RLock() + defer c.RUnlock() + return c.config.InterceptUnknown } // GetFirewallType returns the firewall to use func (c *Client) GetFirewallType() string { - clientConfig.RLock() - defer clientConfig.RUnlock() - if clientConfig.Firewall == "" { + c.RLock() + defer c.RUnlock() + if c.config.Firewall == "" { return iptables.Name } - return clientConfig.Firewall + return c.config.Firewall } // DefaultAction returns the default configured action for @@ -255,7 +254,7 @@ func (c *Client) openSocket() (err error) { c.Lock() defer c.Unlock() - dialOption, err := auth.New(&clientConfig) + dialOption, err := auth.New(&c.config) if err != nil { return fmt.Errorf("Invalid client auth options: %s", err) } diff --git a/daemon/ui/config/config.go b/daemon/ui/config/config.go index 2bee27fd..ff21593e 100644 --- a/daemon/ui/config/config.go +++ b/daemon/ui/config/config.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "os" "reflect" - "sync" "github.com/evilsocket/opensnitch/daemon/log" "github.com/evilsocket/opensnitch/daemon/log/loggers" @@ -14,7 +13,8 @@ import ( ) type ( - serverTLSOptions struct { + // ServerTLSOptions struct + ServerTLSOptions struct { CACert string `json:"CACert"` ServerCert string `json:"ServerCert"` ServerKey string `json:"ServerKey"` @@ -31,36 +31,42 @@ type ( // VerifyPeerCertificate bool } - serverAuth struct { + // ServerAuth struct + ServerAuth struct { // token?, google?, simple-tls, mutual-tls Type string `json:"Type"` - TLSOptions serverTLSOptions `json:"TLSOptions"` + TLSOptions ServerTLSOptions `json:"TLSOptions"` } - serverConfig struct { + // ServerConfig struct + ServerConfig struct { Address string `json:"Address"` - Authentication serverAuth `json:"Authentication"` + Authentication ServerAuth `json:"Authentication"` LogFile string `json:"LogFile"` Loggers []loggers.LoggerConfig `json:"Loggers"` } - rulesOptions struct { + // RulesOptions struct + RulesOptions struct { Path string `json:"Path"` EnableChecksums bool `json:"EnableChecksums"` } - fwOptions struct { + // FwOptions struct + FwOptions struct { Firewall string `json:"Firewall"` ConfigPath string `json:"ConfigPath"` BypassQueue string `json:"BypassQueue"` MonitorInterval string `json:"MonitorInterval"` } - ebpfOptions struct { + // EbpfOptions struct + EbpfOptions struct { ModulesPath string `json:"ModulesPath"` } - internalOptions struct { + // InternalOptions struct + InternalOptions struct { GCPercent int `json:"GCPercent"` } ) @@ -72,18 +78,16 @@ type Config struct { DefaultAction string `json:"DefaultAction"` DefaultDuration string `json:"DefaultDuration"` ProcMonitorMethod string `json:"ProcMonitorMethod"` - FwOptions fwOptions `json:"FwOptions"` - Ebpf ebpfOptions `json:"Ebpf"` - Server serverConfig `json:"Server"` - Rules rulesOptions `json:"Rules"` + FwOptions FwOptions `json:"FwOptions"` + Ebpf EbpfOptions `json:"Ebpf"` + Server ServerConfig `json:"Server"` + Rules RulesOptions `json:"Rules"` + Internal InternalOptions `json:"Internal"` Stats statistics.StatsConfig `json:"Stats"` - Internal internalOptions `json:"Internal"` InterceptUnknown bool `json:"InterceptUnknown"` LogUTC bool `json:"LogUTC"` LogMicro bool `json:"LogMicro"` - - sync.RWMutex } // Parse determines if the given configuration is ok. diff --git a/daemon/ui/config_utils.go b/daemon/ui/config_utils.go index 555c9b0e..c9ed928b 100644 --- a/daemon/ui/config_utils.go +++ b/daemon/ui/config_utils.go @@ -2,6 +2,7 @@ package ui import ( "fmt" + "reflect" "strings" "runtime/debug" @@ -41,10 +42,10 @@ func (c *Client) setSocketPath(socketPath string) { } func (c *Client) isProcMonitorEqual(newMonitorMethod string) bool { - clientConfig.RLock() - defer clientConfig.RUnlock() + c.RLock() + defer c.RUnlock() - return newMonitorMethod == clientConfig.ProcMonitorMethod + return newMonitorMethod == c.config.ProcMonitorMethod } func (c *Client) loadDiskConfiguration(reload bool) { @@ -83,7 +84,9 @@ func (c *Client) loadConfiguration(reload bool, rawConfig []byte) error { if err := c.reloadConfiguration(reload, newConfig); err != nil { return fmt.Errorf("reloading configuration: %s", err.Msg) } - clientConfig = newConfig + c.Lock() + c.config = newConfig + c.Unlock() return nil } @@ -100,15 +103,24 @@ func (c *Client) reloadConfiguration(reload bool, newConfig config.Config) *moni log.Close() log.OpenFile(newConfig.Server.LogFile) } + if !reflect.DeepEqual(c.config.Server.Loggers, newConfig.Server.Loggers) { + log.Debug("[config] reloading config.server.loggers") + c.loggers.Stop() + c.loggers.Load(newConfig.Server.Loggers) + c.stats.SetLoggers(c.loggers) + } else { + log.Debug("[config] config.server.loggers not changed") + } - reconnect := newConfig.Server.Authentication.Type != clientConfig.Server.Authentication.Type || - newConfig.Server.Authentication.TLSOptions.CACert != clientConfig.Server.Authentication.TLSOptions.CACert || - newConfig.Server.Authentication.TLSOptions.ServerCert != clientConfig.Server.Authentication.TLSOptions.ServerCert || - newConfig.Server.Authentication.TLSOptions.ServerKey != clientConfig.Server.Authentication.TLSOptions.ServerKey || - newConfig.Server.Authentication.TLSOptions.ClientCert != clientConfig.Server.Authentication.TLSOptions.ClientCert || - newConfig.Server.Authentication.TLSOptions.ClientKey != clientConfig.Server.Authentication.TLSOptions.ClientKey || - newConfig.Server.Authentication.TLSOptions.ClientAuthType != clientConfig.Server.Authentication.TLSOptions.ClientAuthType || - newConfig.Server.Authentication.TLSOptions.SkipVerify != clientConfig.Server.Authentication.TLSOptions.SkipVerify + if !reflect.DeepEqual(newConfig.Stats, c.config.Stats) { + log.Debug("[config] reloading config.stats") + c.stats.SetLimits(newConfig.Stats) + } else { + log.Debug("[config] config.stats not changed") + } + + reconnect := newConfig.Server.Authentication.Type != c.config.Server.Authentication.Type || + !reflect.DeepEqual(newConfig.Server.Authentication.TLSOptions, c.config.Server.Authentication.TLSOptions) if newConfig.Server.Address != "" { tempSocketPath := c.getSocketPath(newConfig.Server.Address) @@ -137,7 +149,7 @@ func (c *Client) reloadConfiguration(reload bool, newConfig config.Config) *moni clientErrorRule.Duration = rule.Duration(newConfig.DefaultDuration) } - if newConfig.Internal.GCPercent > 0 && newConfig.Internal.GCPercent != clientConfig.Internal.GCPercent { + if newConfig.Internal.GCPercent > 0 && newConfig.Internal.GCPercent != c.config.Internal.GCPercent { oldgcpercent := debug.SetGCPercent(newConfig.Internal.GCPercent) log.Debug("[config] GC percent set to %d, previously was %d", newConfig.Internal.GCPercent, oldgcpercent) } else { @@ -145,23 +157,16 @@ func (c *Client) reloadConfiguration(reload bool, newConfig config.Config) *moni } c.rules.EnableChecksums(newConfig.Rules.EnableChecksums) - if clientConfig.Rules.Path != newConfig.Rules.Path { + if c.config.Rules.Path != newConfig.Rules.Path { c.rules.Reload(newConfig.Rules.Path) log.Debug("[config] reloading config.rules.path: %s", newConfig.Rules.Path) } else { log.Debug("[config] config.rules.path not changed") } - // TODO: - //c.stats.SetLimits(clientConfig.Stats) - if reload { - c.loggers.Stop() - } - c.loggers.Load(clientConfig.Server.Loggers, clientConfig.Stats.Workers) - c.stats.SetLoggers(c.loggers) if reload && c.GetFirewallType() != newConfig.Firewall || - newConfig.FwOptions.ConfigPath != clientConfig.FwOptions.ConfigPath || - newConfig.FwOptions.MonitorInterval != clientConfig.FwOptions.MonitorInterval { + newConfig.FwOptions.ConfigPath != c.config.FwOptions.ConfigPath || + newConfig.FwOptions.MonitorInterval != c.config.FwOptions.MonitorInterval { log.Debug("[config] reloading config.firewall") firewall.Reload( @@ -174,15 +179,15 @@ func (c *Client) reloadConfiguration(reload bool, newConfig config.Config) *moni } reloadProc := false - if clientConfig.ProcMonitorMethod == "" || - newConfig.ProcMonitorMethod != clientConfig.ProcMonitorMethod { - log.Debug("[config] reloading config.ProcMonMethod, old: %s -> new: %s", clientConfig.ProcMonitorMethod, newConfig.ProcMonitorMethod) + if c.config.ProcMonitorMethod == "" || + newConfig.ProcMonitorMethod != c.config.ProcMonitorMethod { + log.Debug("[config] reloading config.ProcMonMethod, old: %s -> new: %s", c.config.ProcMonitorMethod, newConfig.ProcMonitorMethod) reloadProc = true } else { log.Debug("[config] config.ProcMonMethod not changed") } - if reload && procmon.MethodIsEbpf() && newConfig.Ebpf.ModulesPath != "" && clientConfig.Ebpf.ModulesPath != newConfig.Ebpf.ModulesPath { + if reload && procmon.MethodIsEbpf() && newConfig.Ebpf.ModulesPath != "" && c.config.Ebpf.ModulesPath != newConfig.Ebpf.ModulesPath { log.Debug("[config] reloading config.Ebpf.ModulesPath: %s", newConfig.Ebpf.ModulesPath) reloadProc = true } else {