diff --git a/daemon/firewall/common/common.go b/daemon/firewall/common/common.go index 67e3afff..017cfa8e 100644 --- a/daemon/firewall/common/common.go +++ b/daemon/firewall/common/common.go @@ -16,6 +16,9 @@ var ( RestoreChains = true BackupChains = true ReloadConf = true + + DefaultCheckInterval = 10 * time.Second + RulesCheckerDisabled = "0s" ) type ( @@ -25,13 +28,14 @@ type ( // Common holds common fields and functionality of both firewalls, // iptables and nftables. Common struct { - RulesChecker *time.Ticker - ErrChan chan string - QueueNum uint16 - stopChecker chan bool - Running bool - Intercepting bool - FwEnabled bool + RulesChecker *time.Ticker + RulesCheckInterval time.Duration + ErrChan chan string + stopChecker chan bool + QueueNum uint16 + Running bool + Intercepting bool + FwEnabled bool sync.RWMutex } ) @@ -67,6 +71,17 @@ func (c *Common) SendError(err string) { } } +func (c *Common) SetRulesCheckerInterval(interval string) { + dur, err := time.ParseDuration(interval) + if err != nil { + log.Warning("Invalid rules checker interval (falling back to %s): %s", DefaultCheckInterval, err) + c.RulesCheckInterval = DefaultCheckInterval + return + } + + c.RulesCheckInterval = dur +} + // SetQueueNum sets the queue number used by the firewall. // It's the queue where all intercepted connections will be sent. func (c *Common) SetQueueNum(qNum *int) { @@ -109,6 +124,10 @@ func (c *Common) IsIntercepting() bool { func (c *Common) NewRulesChecker(areRulesLoaded callbackBool, reloadRules callback) { c.Lock() defer c.Unlock() + if c.RulesCheckInterval.String() == RulesCheckerDisabled { + log.Info("Fw rules checker disabled ...") + return + } if c.RulesChecker != nil { c.RulesChecker.Stop() @@ -119,7 +138,8 @@ func (c *Common) NewRulesChecker(areRulesLoaded callbackBool, reloadRules callba } } c.stopChecker = make(chan bool, 1) - c.RulesChecker = time.NewTicker(time.Second * 10) + log.Info("Starting new fw checker every %s ...", DefaultCheckInterval) + c.RulesChecker = time.NewTicker(c.RulesCheckInterval) go startCheckingRules(c.stopChecker, c.RulesChecker, areRulesLoaded, reloadRules) } diff --git a/daemon/firewall/config/config.go b/daemon/firewall/config/config.go index e25a63a5..63b1aa07 100644 --- a/daemon/firewall/config/config.go +++ b/daemon/firewall/config/config.go @@ -118,10 +118,10 @@ type SystemConfig struct { // This is the configuration to manage the system firewall (iptables, nftables). type Config struct { sync.Mutex - file string watcher *fsnotify.Watcher - monitorExitChan chan bool SysConfig SystemConfig + monitorExitChan chan bool + file string // preloadCallback is called before reloading the configuration, // in order to delete old fw rules. @@ -132,7 +132,7 @@ type Config struct { } // NewSystemFwConfig initializes config fields -func (c *Config) NewSystemFwConfig(preLoadCb, reLoadCb func()) (*Config, error) { +func (c *Config) NewSystemFwConfig(configPath string, preLoadCb, reLoadCb func()) (*Config, error) { var err error watcher, err := fsnotify.NewWatcher() if err != nil { @@ -143,7 +143,7 @@ func (c *Config) NewSystemFwConfig(preLoadCb, reLoadCb func()) (*Config, error) c.Lock() defer c.Unlock() - c.file = "/etc/opensnitchd/system-fw.json" + c.file = configPath c.monitorExitChan = make(chan bool, 1) c.preloadCallback = preLoadCb c.reloadCallback = reLoadCb @@ -151,7 +151,13 @@ func (c *Config) NewSystemFwConfig(preLoadCb, reLoadCb func()) (*Config, error) return c, nil } -func (c *Config) SetFile(file string) { +// SetConfigFile sets the absolute path to the configuration file to use. +// If it's empty, it'll be ignored (when changing the fw type for example). +func (c *Config) SetConfigFile(file string) { + if file == "" { + log.Debug("Firewall configuration file not provided, ignoring") + return + } c.file = file } diff --git a/daemon/firewall/iptables/iptables.go b/daemon/firewall/iptables/iptables.go index d4b9dcc8..8bc861cb 100644 --- a/daemon/firewall/iptables/iptables.go +++ b/daemon/firewall/iptables/iptables.go @@ -96,17 +96,18 @@ func (ipt *Iptables) Name() string { // Init inserts the firewall rules and starts monitoring for firewall // changes. -func (ipt *Iptables) Init(qNum *int) { +func (ipt *Iptables) Init(qNum *int, configPath, monitorInterval string) { if ipt.IsRunning() { return } ipt.SetQueueNum(qNum) + ipt.SetRulesCheckerInterval(monitorInterval) ipt.ErrChan = make(chan string, 100) // In order to clean up any existing firewall rule before start, // we need to load the fw configuration first to know what rules // were configured. - ipt.NewSystemFwConfig(ipt.preloadConfCallback, ipt.reloadRulesCallback) + ipt.NewSystemFwConfig(configPath, ipt.preloadConfCallback, ipt.reloadRulesCallback) ipt.LoadDiskConfiguration(!common.ReloadConf) // start from a clean state @@ -119,6 +120,7 @@ func (ipt *Iptables) Init(qNum *int) { // Stop deletes the firewall rules, allowing network traffic. func (ipt *Iptables) Stop() { + ipt.ErrChan = make(chan string, 100) if ipt.Running == false { return } diff --git a/daemon/firewall/nftables/nftables.go b/daemon/firewall/nftables/nftables.go index d3e9d6d8..32b91938 100644 --- a/daemon/firewall/nftables/nftables.go +++ b/daemon/firewall/nftables/nftables.go @@ -71,19 +71,20 @@ func (n *Nft) Name() string { // Init inserts the firewall rules and starts monitoring for firewall // changes. -func (n *Nft) Init(qNum *int) { +func (n *Nft) Init(qNum *int, configPath, monitorInterval string) { if n.IsRunning() { return } + n.Conn = NewNft() n.ErrChan = make(chan string, 100) InitMapsStore() n.SetQueueNum(qNum) - n.Conn = NewNft() + n.SetRulesCheckerInterval(monitorInterval) // In order to clean up any existing firewall rule before start, // we need to load the fw configuration first to know what rules // were configured. - n.NewSystemFwConfig(n.PreloadConfCallback, n.ReloadConfCallback) + n.NewSystemFwConfig(configPath, n.PreloadConfCallback, n.ReloadConfCallback) n.LoadDiskConfiguration(!common.ReloadConf) // start from a clean state @@ -98,6 +99,7 @@ func (n *Nft) Init(qNum *int) { // Stop deletes the firewall rules, allowing network traffic. func (n *Nft) Stop() { + n.ErrChan = make(chan string, 100) if n.IsRunning() == false { return } diff --git a/daemon/firewall/nftables/system_test.go b/daemon/firewall/nftables/system_test.go index 59d2f013..1e787f4a 100644 --- a/daemon/firewall/nftables/system_test.go +++ b/daemon/firewall/nftables/system_test.go @@ -14,6 +14,10 @@ type sysChainsListT struct { expectedRules int } +var ( + configFile = "./testdata/test-sysfw-conf.json" +) + func TestAddSystemRules(t *testing.T) { nftest.SkipIfNotPrivileged(t) @@ -21,12 +25,12 @@ func TestAddSystemRules(t *testing.T) { defer nftest.CleanupSystemConn(t, newNS) nftest.Fw.Conn = conn - cfg, err := nftest.Fw.NewSystemFwConfig(nftest.Fw.PreloadConfCallback, nftest.Fw.ReloadConfCallback) + cfg, err := nftest.Fw.NewSystemFwConfig(configFile, nftest.Fw.PreloadConfCallback, nftest.Fw.ReloadConfCallback) if err != nil { t.Logf("Error creating fw config: %s", err) } - cfg.SetFile("./testdata/test-sysfw-conf.json") + cfg.SetConfigFile("./testdata/test-sysfw-conf.json") if err := cfg.LoadDiskConfiguration(false); err != nil { t.Errorf("Error loading config from disk: %s", err) } @@ -69,12 +73,12 @@ func TestFwConfDisabled(t *testing.T) { defer nftest.CleanupSystemConn(t, newNS) nftest.Fw.Conn = conn - cfg, err := nftest.Fw.NewSystemFwConfig(nftest.Fw.PreloadConfCallback, nftest.Fw.ReloadConfCallback) + cfg, err := nftest.Fw.NewSystemFwConfig(configFile, nftest.Fw.PreloadConfCallback, nftest.Fw.ReloadConfCallback) if err != nil { t.Logf("Error creating fw config: %s", err) } - cfg.SetFile("./testdata/test-sysfw-conf.json") + cfg.SetConfigFile("./testdata/test-sysfw-conf.json") if err := cfg.LoadDiskConfiguration(false); err != nil { t.Errorf("Error loading config from disk: %s", err) } @@ -108,12 +112,12 @@ func TestDeleteSystemRules(t *testing.T) { defer nftest.CleanupSystemConn(t, newNS) nftest.Fw.Conn = conn - cfg, err := nftest.Fw.NewSystemFwConfig(nftest.Fw.PreloadConfCallback, nftest.Fw.ReloadConfCallback) + cfg, err := nftest.Fw.NewSystemFwConfig(configFile, nftest.Fw.PreloadConfCallback, nftest.Fw.ReloadConfCallback) if err != nil { t.Logf("Error creating fw config: %s", err) } - cfg.SetFile("./testdata/test-sysfw-conf.json") + cfg.SetConfigFile("./testdata/test-sysfw-conf.json") if err := cfg.LoadDiskConfiguration(false); err != nil { t.Errorf("Error loading config from disk: %s", err) } diff --git a/daemon/firewall/rules.go b/daemon/firewall/rules.go index f7644797..222e9eda 100644 --- a/daemon/firewall/rules.go +++ b/daemon/firewall/rules.go @@ -12,7 +12,7 @@ import ( // Firewall is the interface that all firewalls (iptables, nftables) must implement. type Firewall interface { - Init(*int) + Init(*int, string, string) Stop() Name() string IsRunning() bool @@ -45,7 +45,7 @@ var ( // We'll try to use the firewall configured in the configuration (iptables/nftables). // If iptables is not installed, we can add nftables rules directly to the kernel, // without relying on any binaries. -func Init(fwType string, qNum *int) (err error) { +func Init(fwType, configPath, monitorInterval string, qNum *int) (err error) { if fwType == iptables.Name { fw, err = iptables.Fw() if err != nil { @@ -68,7 +68,7 @@ func Init(fwType string, qNum *int) (err error) { return fmt.Errorf("Firewall not initialized") } fw.Stop() - fw.Init(qNum) + fw.Init(qNum, configPath, monitorInterval) queueNum = *qNum log.Info("Using %s firewall", fw.Name()) @@ -99,19 +99,13 @@ func CleanRules(logErrors bool) { fw.CleanRules(logErrors) } -// ChangeFw stops current firewall and initializes a new one. -func ChangeFw(fwtype string) (err error) { +// Reload stops current firewall and initializes a new one. +func Reload(fwtype, configPath, monitorInterval string) (err error) { Stop() - err = Init(fwtype, &queueNum) + err = Init(fwtype, configPath, monitorInterval, &queueNum) return } -// Reload deletes existing firewall rules and readds them. -func Reload() { - fw.Stop() - fw.Init(&queueNum) -} - // ReloadSystemRules deletes existing rules, and add them again func ReloadSystemRules() { fw.DeleteSystemRules(!common.ForcedDelRules, common.RestoreChains, true) diff --git a/daemon/main.go b/daemon/main.go index 22c11f21..e04af2ed 100644 --- a/daemon/main.go +++ b/daemon/main.go @@ -63,6 +63,7 @@ var ( logMicro = false rulesPath = "/etc/opensnitchd/rules/" configFile = "/etc/opensnitchd/default-config.json" + fwConfigFile = "/etc/opensnitchd/system-fw.json" noLiveReload = false queueNum = 0 repeatQueueNum int //will be set later to queueNum + 1 @@ -105,6 +106,7 @@ func init() { flag.BoolVar(&noLiveReload, "no-live-reload", debug, "Disable rules live reloading.") flag.StringVar(&configFile, "config-file", configFile, "Path to the daemon configuration file.") + flag.StringVar(&fwConfigFile, "fw-config-file", fwConfigFile, "Path to the system fw configuration file.") flag.StringVar(&logFile, "log-file", logFile, "Write logs to this file instead of the standard output.") flag.BoolVar(&logUTC, "log-utc", logUTC, "Write logs output with UTC timezone (enabled by default).") flag.BoolVar(&logMicro, "log-micro", logMicro, "Write logs output with microsecond timestamp (disabled by default).") @@ -556,8 +558,17 @@ func main() { } repeatPktChan = repeatQueue.Packets() + fwConfigPath := fwConfigFile + if cfg.FwOptions.ConfigPath != "" { + fwConfigPath = cfg.FwOptions.ConfigPath + } + log.Info("Using system fw configuration %s ...", fwConfigPath) // queue is ready, run firewall rules and start intercepting connections - if err = firewall.Init(uiClient.GetFirewallType(), &queueNum); err != nil { + if err = firewall.Init( + uiClient.GetFirewallType(), + fwConfigPath, + cfg.FwOptions.MonitorInterval, + &queueNum); err != nil { log.Warning("%s", err) uiClient.SendWarningAlert(err) } diff --git a/daemon/ui/config/config.go b/daemon/ui/config/config.go index 9730c203..37a1ce2c 100644 --- a/daemon/ui/config/config.go +++ b/daemon/ui/config/config.go @@ -49,16 +49,24 @@ type rulesOptions struct { EnableChecksums bool `json:"EnableChecksums"` } +type fwOptions struct { + Firewall string `json:"Firewall"` + ConfigPath string `json:"ConfigPath"` + ActionOnOverflow string `json:"ActionOnOverflow"` + MonitorInterval string `json:"MonitorInterval"` +} + // Config holds the values loaded from configFile type Config struct { sync.RWMutex Server serverConfig `json:"Server"` Stats statistics.StatsConfig `json:"Stats"` - Rules rulesOptions `json:"Rules"` DefaultAction string `json:"DefaultAction"` DefaultDuration string `json:"DefaultDuration"` ProcMonitorMethod string `json:"ProcMonitorMethod"` + Rules rulesOptions `json:"Rules"` Firewall string `json:"Firewall"` + FwOptions fwOptions `json:"FwOptions"` LogLevel *int32 `json:"LogLevel"` InterceptUnknown bool `json:"InterceptUnknown"` LogUTC bool `json:"LogUTC"` diff --git a/daemon/ui/config_utils.go b/daemon/ui/config_utils.go index c39505cf..52564f80 100644 --- a/daemon/ui/config_utils.go +++ b/daemon/ui/config_utils.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/evilsocket/opensnitch/daemon/firewall" "github.com/evilsocket/opensnitch/daemon/log" "github.com/evilsocket/opensnitch/daemon/procmon/monitor" "github.com/evilsocket/opensnitch/daemon/rule" @@ -60,6 +61,11 @@ func (c *Client) loadDiskConfiguration(reload bool) { } if reload { + firewall.Reload( + clientConfig.Firewall, + clientConfig.FwOptions.ConfigPath, + clientConfig.FwOptions.MonitorInterval, + ) return } diff --git a/daemon/ui/notifications.go b/daemon/ui/notifications.go index 8df85344..dd6199a0 100644 --- a/daemon/ui/notifications.go +++ b/daemon/ui/notifications.go @@ -110,7 +110,11 @@ func (c *Client) handleActionChangeConfig(stream protocol.UI_NotificationsClient } if c.GetFirewallType() != newConf.Firewall { - firewall.ChangeFw(newConf.Firewall) + firewall.Reload( + newConf.Firewall, + newConf.FwOptions.ConfigPath, + newConf.FwOptions.MonitorInterval, + ) } if err := monitor.ReconfigureMonitorMethod(newConf.ProcMonitorMethod); err != nil {