Skip to content

Commit

Permalink
improved rules reloading, cli parameters
Browse files Browse the repository at this point in the history
 - When reloading rules from a path:
   stop existing (domains,ips,regexp) lists monitors, stop rules
   watcher and start watching the new dir for changes, delete existing
   rules from memory, etc.
 - Previously, cli parameters (queue number, log file, etc) were taking
   into account before loading the configuration.
   Now the configuration file is loaded first (default-config.json), and
   if any of the cli parameter has been specified, it'll overwrite the
   loaded configuration from file.

   This means for example that if you use "-process-monitor-method proc",
   and "ebpf" is configured in default-config.json, firstly "ebpf" will
   be configured, and later "proc".

   (-queue-num option for now requires to match config option
   cfg.FwOptions.QueueNumber)
  • Loading branch information
gustavo-iniguez-goya committed May 21, 2024
1 parent 661e3da commit c0d1da2
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 60 deletions.
77 changes: 46 additions & 31 deletions daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ var (
logFile = ""
logUTC = true
logMicro = false
rulesPath = "/etc/opensnitchd/rules/"
rulesPath = ""
configFile = "/etc/opensnitchd/default-config.json"
fwConfigFile = "/etc/opensnitchd/system-fw.json"
fwConfigFile = ""
ebpfModPath = "" // /usr/lib/opensnitchd/ebpf
noLiveReload = false
queueNum = 0
Expand Down Expand Up @@ -103,7 +103,7 @@ func init() {
flag.BoolVar(&showVersion, "version", debug, "Show daemon version of this executable and exit.")
flag.BoolVar(&checkRequirements, "check-requirements", debug, "Check system requirements for incompatibilities.")

flag.StringVar(&procmonMethod, "process-monitor-method", procmonMethod, "How to search for processes path. Options: ftrace, audit (experimental), ebpf (experimental), proc (default)")
flag.StringVar(&procmonMethod, "process-monitor-method", procmonMethod, "Options: audit, ebpf, proc (default)")
flag.StringVar(&uiSocket, "ui-socket", uiSocket, "Path the UI gRPC service listener (https://github.com/grpc/grpc/blob/master/doc/naming.md).")
flag.IntVar(&queueNum, "queue-num", queueNum, "Netfilter queue number.")
flag.IntVar(&workers, "workers", workers, "Number of concurrent workers.")
Expand Down Expand Up @@ -151,6 +151,20 @@ func overwriteLogging() bool {
return debug || warning || important || errorlog || logFile != "" || logMicro
}

// overwriteFw reloads the fw with the configuration file specified via cli.
func overwriteFw(cfg *config.Config, qNum uint16, fwCfg string) {
firewall.Reload(
cfg.Firewall,
fwCfg,
cfg.FwOptions.MonitorInterval,
qNum,
)
// TODO: Close() closes the daemon if closing the queue timeouts
//queue.Close()
//repeatQueue.Close()
//setupQueues(qNum)
}

func setupQueues(qNum uint16) {
// prepare the queue
var err error
Expand Down Expand Up @@ -557,6 +571,7 @@ func main() {

setupLogging()
setupProfiling()
setupSignals()

log.Important("Starting %s v%s", core.Name, core.Version)

Expand All @@ -565,55 +580,55 @@ func main() {
log.Fatal("%s", err)
}

if err == nil && cfg.Rules.Path != "" {
rulesPath = cfg.Rules.Path
}
if rulesPath == "" {
log.Fatal("rules path cannot be empty")
}

rulesPath, err := core.ExpandPath(rulesPath)
if err != nil {
log.Fatal("Error accessing rules path (does it exist?): %s", err)
}

if cfg.FwOptions.ConfigPath == "" {
cfg.FwOptions.ConfigPath = fwConfigFile
}
log.Info("Using system fw configuration %s ...", fwConfigFile)

if uint16(queueNum) != cfg.FwOptions.QueueNum && queueNum > 0 {
cfg.FwOptions.QueueNum = uint16(queueNum)
}

setupSignals()

log.Info("Loading rules from %s ...", rulesPath)
log.Info("Loading rules from %s ...", cfg.Rules.Path)
rules, err = rule.NewLoader(!noLiveReload)
if err != nil {
log.Fatal("%s", err)
} else if err = rules.Load(rulesPath); err != nil {
log.Fatal("%s", err)
}
stats = statistics.New(rules)
loggerMgr = loggers.NewLoggerManager()
stats.SetLoggers(loggerMgr)
uiClient = ui.NewClient(uiSocket, configFile, stats, rules, loggerMgr)

// default expected queue from the cli is 0. If it's greater than 0
// overwrite config value (which by default is also 0)
qNum := cfg.FwOptions.QueueNum
if uint16(queueNum) != cfg.FwOptions.QueueNum && queueNum > 0 {
qNum = uint16(queueNum)
}
log.Info("Using queue number %d ...", qNum)

setupWorkers()
setupQueues(cfg.FwOptions.QueueNum)
setupQueues(qNum)

// queue and firewall rules should be ready by now

uiClient.Connect()
listenToEvents()

// overwrite configuration options with the ones specified from the cli

if overwriteLogging() {
setupLogging()
}

if fwConfigFile != "" {
log.Info("Reloading fw rules from %s, queue %d ...", fwConfigFile, qNum)
overwriteFw(cfg, qNum, fwConfigFile)
}
log.Info("Using system fw configuration %s ...", fwConfigFile)

if rulesPath != "" {
log.Info("Reloading rules from %s ...", rulesPath)
if err := rules.Reload(rulesPath); err != nil {
log.Fatal("Error loading rules path %s", rulesPath)
}
}

// overwrite monitor method from configuration if the user has passed
// the option via command line.
if procmonMethod != "" {
if procmonMethod != "" || (ebpfModPath != "" && ebpfModPath != cfg.Ebpf.ModulesPath) {
log.Info("Reloading proc monitor (%s) (ebpf mods path: %s)...", procmonMethod, cfg.Ebpf.ModulesPath)
if err := monitor.ReconfigureMonitorMethod(procmonMethod, cfg.Ebpf); err != nil {
msg := fmt.Sprintf("Unable to set process monitor method via parameter: %v", err)
uiClient.SendWarningAlert(msg)
Expand Down
48 changes: 39 additions & 9 deletions daemon/rule/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import (
type Loader struct {
watcher *fsnotify.Watcher
rules map[string]*Rule
path string
rulesKeys []string
Path string
liveReload bool
liveReloadRunning bool
checkSums bool
stopLiveReload chan struct{}

sync.RWMutex
}
Expand All @@ -42,11 +43,12 @@ func NewLoader(liveReload bool) (*Loader, error) {
return nil, err
}
return &Loader{
path: "",
Path: "",
rules: make(map[string]*Rule),
liveReload: liveReload,
watcher: watcher,
liveReloadRunning: false,
stopLiveReload: make(chan struct{}),
}, nil
}

Expand Down Expand Up @@ -86,13 +88,35 @@ func (l *Loader) HasChecksums(op Operand) {
// Reload loads rules from the specified path, deleting existing loaded
// rules from memory.
func (l *Loader) Reload(path string) error {
log.Info("rules.Loader.Reload(): %s", path)

// check that the new path exists before reloading
if core.Exists(path) == false {
return fmt.Errorf("The new path '%s' does not exist", path)
}

// stop monitors
if l.liveReloadRunning {
l.stopLiveReload <- struct{}{}
}
if l.watcher != nil {
l.watcher.Remove(l.Path)
}
for _, r := range l.rules {
l.cleanListsRule(r)
}

// then delete the rules, and reload everything
l.Lock()
l.rulesKeys = make([]string, 0)
l.rules = make(map[string]*Rule)
l.Unlock()
return l.Load(path)
}

// Load loads rules files from disk.
func (l *Loader) Load(path string) error {
log.Debug("rules.Loader.Load(): %s", path)
if core.Exists(path) == false {
return fmt.Errorf("Path '%s' does not exist\nCreate it if you want to save rules to disk", path)
}
Expand All @@ -107,7 +131,7 @@ func (l *Loader) Load(path string) error {
return fmt.Errorf("Error globbing '%s': %s", expr, err)
}

l.path = path
l.Path = path
if len(l.rules) == 0 {
l.rules = make(map[string]*Rule)
}
Expand All @@ -132,7 +156,7 @@ func (l *Loader) Load(path string) error {
func (l *Loader) Add(rule *Rule, saveToDisk bool) error {
l.addUserRule(rule)
if saveToDisk {
fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name))
fileName := filepath.Join(l.Path, fmt.Sprintf("%s.json", rule.Name))
return l.Save(rule, fileName)
}
return nil
Expand All @@ -147,7 +171,7 @@ func (l *Loader) Replace(rule *Rule, saveToDisk bool) error {
l.Lock()
defer l.Unlock()

fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name))
fileName := filepath.Join(l.Path, fmt.Sprintf("%s.json", rule.Name))
return l.Save(rule, fileName)
}
return nil
Expand Down Expand Up @@ -263,7 +287,7 @@ func (l *Loader) deleteRule(filePath string) {
}

func (l *Loader) deleteRuleFromDisk(ruleName string) error {
path := fmt.Sprint(l.path, "/", ruleName, ".json")
path := fmt.Sprint(l.Path, "/", ruleName, ".json")
return os.Remove(path)
}

Expand All @@ -277,7 +301,8 @@ func (l *Loader) deleteOldRuleFromDisk(oldRule, newRule *Rule) {
}
}

// cleanListsRule erases the list of domains of an Operator of type Lists
// cleanListsRule erases the lists loaded of an Operator of type Lists,
// and stops the workers monitoring the lists.
func (l *Loader) cleanListsRule(oldRule *Rule) {
if oldRule.Operator.Type == Lists {
oldRule.Operator.StopMonitoringLists()
Expand Down Expand Up @@ -412,15 +437,17 @@ func (l *Loader) scheduleTemporaryRule(rule Rule) error {
func (l *Loader) liveReloadWorker() {
l.liveReloadRunning = true

log.Debug("Rules watcher started on path %s ...", l.path)
if err := l.watcher.Add(l.path); err != nil {
log.Debug("Rules watcher started on path %s ...", l.Path)
if err := l.watcher.Add(l.Path); err != nil {
log.Error("Could not watch path: %s", err)
l.liveReloadRunning = false
return
}

for {
select {
case <-l.stopLiveReload:
goto Exit
case event := <-l.watcher.Events:
// a new rule json file has been created or updated
if event.Op&fsnotify.Write == fsnotify.Write {
Expand All @@ -442,6 +469,9 @@ func (l *Loader) liveReloadWorker() {
log.Error("File system watcher error: %s", err)
}
}
Exit:
log.Debug("[rules] liveReloadWorker() exited")
l.liveReloadRunning = false
}

// FindFirstMatch will try match the connection against the existing rule set.
Expand Down
2 changes: 1 addition & 1 deletion daemon/rule/operator_lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (o *Operator) monitorLists() {
Exit:
modTimes = nil
o.ClearLists()
log.Info("lists monitor stopped")
log.Info("lists monitor stopped: %s", o.Data)
}

// ClearLists deletes all the entries of a list
Expand Down
1 change: 1 addition & 0 deletions daemon/ui/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func NewClient(socketPath, localConfigFile string, stats *statistics.Statistics,
isConnected: make(chan bool),
alertsChan: make(chan protocol.Alert, maxQueuedAlerts),
}
c.config.Rules.Path = rules.Path
//for i := 0; i < 4; i++ {
go c.alertsDispatcher()

Expand Down
41 changes: 22 additions & 19 deletions daemon/ui/config_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,32 +157,16 @@ func (c *Client) reloadConfiguration(reload bool, newConfig config.Config) *moni
log.Debug("[config] config.internal.gcpercent not changed")
}

// 1. load rules
c.rules.EnableChecksums(newConfig.Rules.EnableChecksums)
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)
log.Debug("[config] reloading config.rules.path, old: <%s> new: <%s>", c.config.Rules.Path, newConfig.Rules.Path)
} else {
log.Debug("[config] config.rules.path not changed")
}

reloadFw := false
if c.GetFirewallType() != newConfig.Firewall ||
newConfig.FwOptions.ConfigPath != c.config.FwOptions.ConfigPath ||
newConfig.FwOptions.QueueNum != c.config.FwOptions.QueueNum ||
newConfig.FwOptions.MonitorInterval != c.config.FwOptions.MonitorInterval {
log.Debug("[config] reloading config.firewall")
reloadFw = true

firewall.Reload(
newConfig.Firewall,
newConfig.FwOptions.ConfigPath,
newConfig.FwOptions.MonitorInterval,
newConfig.FwOptions.QueueNum,
)
} else {
log.Debug("[config] config.firewall not changed")
}

// 2. load proc mon method
reloadProc := false
if c.config.ProcMonitorMethod == "" ||
newConfig.ProcMonitorMethod != c.config.ProcMonitorMethod {
Expand All @@ -208,6 +192,25 @@ func (c *Client) reloadConfiguration(reload bool, newConfig config.Config) *moni
log.Debug("[config] config.procmon not changed")
}

// 3. load fw
reloadFw := false
if c.GetFirewallType() != newConfig.Firewall ||
newConfig.FwOptions.ConfigPath != c.config.FwOptions.ConfigPath ||
newConfig.FwOptions.QueueNum != c.config.FwOptions.QueueNum ||
newConfig.FwOptions.MonitorInterval != c.config.FwOptions.MonitorInterval {
log.Debug("[config] reloading config.firewall")
reloadFw = true

firewall.Reload(
newConfig.Firewall,
newConfig.FwOptions.ConfigPath,
newConfig.FwOptions.MonitorInterval,
newConfig.FwOptions.QueueNum,
)
} else {
log.Debug("[config] config.firewall not changed")
}

if (reloadProc || reloadFw) && newConfig.Internal.FlushConnsOnStart {
log.Debug("[config] flushing established connections")
netlink.FlushConnections()
Expand Down

0 comments on commit c0d1da2

Please sign in to comment.