fw: allow to configure config file/ check interval

- Allow to configure system firewall configuration file path:
   * via cli (-fw-config-file).
   * via global configuration file.
 - Allow to configure fw rules check interval.

The system fw config file contains regular iptables/nftables rules.
Previously it was hardcoded to /etc/opensnitchd/system-fw.json

The interval to check if the interception rules were added was also
hardcoded to 10 seconds. Now it's possible to configure it.
A value of "0s" disables the interval, while "" defaults to 10 seconds.
This commit is contained in:
Gustavo Iñiguez Goia 2023-12-20 21:32:45 +01:00
parent e905163a03
commit 54ac5a3549
Failed to generate hash of commit
10 changed files with 96 additions and 39 deletions

View file

@ -16,6 +16,9 @@ var (
RestoreChains = true RestoreChains = true
BackupChains = true BackupChains = true
ReloadConf = true ReloadConf = true
DefaultCheckInterval = 10 * time.Second
RulesCheckerDisabled = "0s"
) )
type ( type (
@ -25,13 +28,14 @@ type (
// Common holds common fields and functionality of both firewalls, // Common holds common fields and functionality of both firewalls,
// iptables and nftables. // iptables and nftables.
Common struct { Common struct {
RulesChecker *time.Ticker RulesChecker *time.Ticker
ErrChan chan string RulesCheckInterval time.Duration
QueueNum uint16 ErrChan chan string
stopChecker chan bool stopChecker chan bool
Running bool QueueNum uint16
Intercepting bool Running bool
FwEnabled bool Intercepting bool
FwEnabled bool
sync.RWMutex 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. // SetQueueNum sets the queue number used by the firewall.
// It's the queue where all intercepted connections will be sent. // It's the queue where all intercepted connections will be sent.
func (c *Common) SetQueueNum(qNum *int) { func (c *Common) SetQueueNum(qNum *int) {
@ -109,6 +124,10 @@ func (c *Common) IsIntercepting() bool {
func (c *Common) NewRulesChecker(areRulesLoaded callbackBool, reloadRules callback) { func (c *Common) NewRulesChecker(areRulesLoaded callbackBool, reloadRules callback) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
if c.RulesCheckInterval.String() == RulesCheckerDisabled {
log.Info("Fw rules checker disabled ...")
return
}
if c.RulesChecker != nil { if c.RulesChecker != nil {
c.RulesChecker.Stop() c.RulesChecker.Stop()
@ -119,7 +138,8 @@ func (c *Common) NewRulesChecker(areRulesLoaded callbackBool, reloadRules callba
} }
} }
c.stopChecker = make(chan bool, 1) 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) go startCheckingRules(c.stopChecker, c.RulesChecker, areRulesLoaded, reloadRules)
} }

View file

@ -118,10 +118,10 @@ type SystemConfig struct {
// This is the configuration to manage the system firewall (iptables, nftables). // This is the configuration to manage the system firewall (iptables, nftables).
type Config struct { type Config struct {
sync.Mutex sync.Mutex
file string
watcher *fsnotify.Watcher watcher *fsnotify.Watcher
monitorExitChan chan bool
SysConfig SystemConfig SysConfig SystemConfig
monitorExitChan chan bool
file string
// preloadCallback is called before reloading the configuration, // preloadCallback is called before reloading the configuration,
// in order to delete old fw rules. // in order to delete old fw rules.
@ -132,7 +132,7 @@ type Config struct {
} }
// NewSystemFwConfig initializes config fields // 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 var err error
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
@ -143,7 +143,7 @@ func (c *Config) NewSystemFwConfig(preLoadCb, reLoadCb func()) (*Config, error)
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
c.file = "/etc/opensnitchd/system-fw.json" c.file = configPath
c.monitorExitChan = make(chan bool, 1) c.monitorExitChan = make(chan bool, 1)
c.preloadCallback = preLoadCb c.preloadCallback = preLoadCb
c.reloadCallback = reLoadCb c.reloadCallback = reLoadCb
@ -151,7 +151,13 @@ func (c *Config) NewSystemFwConfig(preLoadCb, reLoadCb func()) (*Config, error)
return c, nil 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 c.file = file
} }

View file

@ -96,17 +96,18 @@ func (ipt *Iptables) Name() string {
// Init inserts the firewall rules and starts monitoring for firewall // Init inserts the firewall rules and starts monitoring for firewall
// changes. // changes.
func (ipt *Iptables) Init(qNum *int) { func (ipt *Iptables) Init(qNum *int, configPath, monitorInterval string) {
if ipt.IsRunning() { if ipt.IsRunning() {
return return
} }
ipt.SetQueueNum(qNum) ipt.SetQueueNum(qNum)
ipt.SetRulesCheckerInterval(monitorInterval)
ipt.ErrChan = make(chan string, 100) ipt.ErrChan = make(chan string, 100)
// In order to clean up any existing firewall rule before start, // In order to clean up any existing firewall rule before start,
// we need to load the fw configuration first to know what rules // we need to load the fw configuration first to know what rules
// were configured. // were configured.
ipt.NewSystemFwConfig(ipt.preloadConfCallback, ipt.reloadRulesCallback) ipt.NewSystemFwConfig(configPath, ipt.preloadConfCallback, ipt.reloadRulesCallback)
ipt.LoadDiskConfiguration(!common.ReloadConf) ipt.LoadDiskConfiguration(!common.ReloadConf)
// start from a clean state // start from a clean state
@ -119,6 +120,7 @@ func (ipt *Iptables) Init(qNum *int) {
// Stop deletes the firewall rules, allowing network traffic. // Stop deletes the firewall rules, allowing network traffic.
func (ipt *Iptables) Stop() { func (ipt *Iptables) Stop() {
ipt.ErrChan = make(chan string, 100)
if ipt.Running == false { if ipt.Running == false {
return return
} }

View file

@ -71,19 +71,20 @@ func (n *Nft) Name() string {
// Init inserts the firewall rules and starts monitoring for firewall // Init inserts the firewall rules and starts monitoring for firewall
// changes. // changes.
func (n *Nft) Init(qNum *int) { func (n *Nft) Init(qNum *int, configPath, monitorInterval string) {
if n.IsRunning() { if n.IsRunning() {
return return
} }
n.Conn = NewNft()
n.ErrChan = make(chan string, 100) n.ErrChan = make(chan string, 100)
InitMapsStore() InitMapsStore()
n.SetQueueNum(qNum) n.SetQueueNum(qNum)
n.Conn = NewNft() n.SetRulesCheckerInterval(monitorInterval)
// In order to clean up any existing firewall rule before start, // In order to clean up any existing firewall rule before start,
// we need to load the fw configuration first to know what rules // we need to load the fw configuration first to know what rules
// were configured. // were configured.
n.NewSystemFwConfig(n.PreloadConfCallback, n.ReloadConfCallback) n.NewSystemFwConfig(configPath, n.PreloadConfCallback, n.ReloadConfCallback)
n.LoadDiskConfiguration(!common.ReloadConf) n.LoadDiskConfiguration(!common.ReloadConf)
// start from a clean state // start from a clean state
@ -98,6 +99,7 @@ func (n *Nft) Init(qNum *int) {
// Stop deletes the firewall rules, allowing network traffic. // Stop deletes the firewall rules, allowing network traffic.
func (n *Nft) Stop() { func (n *Nft) Stop() {
n.ErrChan = make(chan string, 100)
if n.IsRunning() == false { if n.IsRunning() == false {
return return
} }

View file

@ -14,6 +14,10 @@ type sysChainsListT struct {
expectedRules int expectedRules int
} }
var (
configFile = "./testdata/test-sysfw-conf.json"
)
func TestAddSystemRules(t *testing.T) { func TestAddSystemRules(t *testing.T) {
nftest.SkipIfNotPrivileged(t) nftest.SkipIfNotPrivileged(t)
@ -21,12 +25,12 @@ func TestAddSystemRules(t *testing.T) {
defer nftest.CleanupSystemConn(t, newNS) defer nftest.CleanupSystemConn(t, newNS)
nftest.Fw.Conn = conn 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 { if err != nil {
t.Logf("Error creating fw config: %s", err) 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 { if err := cfg.LoadDiskConfiguration(false); err != nil {
t.Errorf("Error loading config from disk: %s", err) t.Errorf("Error loading config from disk: %s", err)
} }
@ -69,12 +73,12 @@ func TestFwConfDisabled(t *testing.T) {
defer nftest.CleanupSystemConn(t, newNS) defer nftest.CleanupSystemConn(t, newNS)
nftest.Fw.Conn = conn 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 { if err != nil {
t.Logf("Error creating fw config: %s", err) 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 { if err := cfg.LoadDiskConfiguration(false); err != nil {
t.Errorf("Error loading config from disk: %s", err) t.Errorf("Error loading config from disk: %s", err)
} }
@ -108,12 +112,12 @@ func TestDeleteSystemRules(t *testing.T) {
defer nftest.CleanupSystemConn(t, newNS) defer nftest.CleanupSystemConn(t, newNS)
nftest.Fw.Conn = conn 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 { if err != nil {
t.Logf("Error creating fw config: %s", err) 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 { if err := cfg.LoadDiskConfiguration(false); err != nil {
t.Errorf("Error loading config from disk: %s", err) t.Errorf("Error loading config from disk: %s", err)
} }

View file

@ -12,7 +12,7 @@ import (
// Firewall is the interface that all firewalls (iptables, nftables) must implement. // Firewall is the interface that all firewalls (iptables, nftables) must implement.
type Firewall interface { type Firewall interface {
Init(*int) Init(*int, string, string)
Stop() Stop()
Name() string Name() string
IsRunning() bool IsRunning() bool
@ -45,7 +45,7 @@ var (
// We'll try to use the firewall configured in the configuration (iptables/nftables). // 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, // If iptables is not installed, we can add nftables rules directly to the kernel,
// without relying on any binaries. // 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 { if fwType == iptables.Name {
fw, err = iptables.Fw() fw, err = iptables.Fw()
if err != nil { if err != nil {
@ -68,7 +68,7 @@ func Init(fwType string, qNum *int) (err error) {
return fmt.Errorf("Firewall not initialized") return fmt.Errorf("Firewall not initialized")
} }
fw.Stop() fw.Stop()
fw.Init(qNum) fw.Init(qNum, configPath, monitorInterval)
queueNum = *qNum queueNum = *qNum
log.Info("Using %s firewall", fw.Name()) log.Info("Using %s firewall", fw.Name())
@ -99,19 +99,13 @@ func CleanRules(logErrors bool) {
fw.CleanRules(logErrors) fw.CleanRules(logErrors)
} }
// ChangeFw stops current firewall and initializes a new one. // Reload stops current firewall and initializes a new one.
func ChangeFw(fwtype string) (err error) { func Reload(fwtype, configPath, monitorInterval string) (err error) {
Stop() Stop()
err = Init(fwtype, &queueNum) err = Init(fwtype, configPath, monitorInterval, &queueNum)
return return
} }
// Reload deletes existing firewall rules and readds them.
func Reload() {
fw.Stop()
fw.Init(&queueNum)
}
// ReloadSystemRules deletes existing rules, and add them again // ReloadSystemRules deletes existing rules, and add them again
func ReloadSystemRules() { func ReloadSystemRules() {
fw.DeleteSystemRules(!common.ForcedDelRules, common.RestoreChains, true) fw.DeleteSystemRules(!common.ForcedDelRules, common.RestoreChains, true)

View file

@ -63,6 +63,7 @@ var (
logMicro = false logMicro = false
rulesPath = "/etc/opensnitchd/rules/" rulesPath = "/etc/opensnitchd/rules/"
configFile = "/etc/opensnitchd/default-config.json" configFile = "/etc/opensnitchd/default-config.json"
fwConfigFile = "/etc/opensnitchd/system-fw.json"
noLiveReload = false noLiveReload = false
queueNum = 0 queueNum = 0
repeatQueueNum int //will be set later to queueNum + 1 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.BoolVar(&noLiveReload, "no-live-reload", debug, "Disable rules live reloading.")
flag.StringVar(&configFile, "config-file", configFile, "Path to the daemon configuration file.") 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.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(&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).") flag.BoolVar(&logMicro, "log-micro", logMicro, "Write logs output with microsecond timestamp (disabled by default).")
@ -556,8 +558,17 @@ func main() {
} }
repeatPktChan = repeatQueue.Packets() 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 // 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) log.Warning("%s", err)
uiClient.SendWarningAlert(err) uiClient.SendWarningAlert(err)
} }

View file

@ -49,16 +49,24 @@ type rulesOptions struct {
EnableChecksums bool `json:"EnableChecksums"` 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 // Config holds the values loaded from configFile
type Config struct { type Config struct {
sync.RWMutex sync.RWMutex
Server serverConfig `json:"Server"` Server serverConfig `json:"Server"`
Stats statistics.StatsConfig `json:"Stats"` Stats statistics.StatsConfig `json:"Stats"`
Rules rulesOptions `json:"Rules"`
DefaultAction string `json:"DefaultAction"` DefaultAction string `json:"DefaultAction"`
DefaultDuration string `json:"DefaultDuration"` DefaultDuration string `json:"DefaultDuration"`
ProcMonitorMethod string `json:"ProcMonitorMethod"` ProcMonitorMethod string `json:"ProcMonitorMethod"`
Rules rulesOptions `json:"Rules"`
Firewall string `json:"Firewall"` Firewall string `json:"Firewall"`
FwOptions fwOptions `json:"FwOptions"`
LogLevel *int32 `json:"LogLevel"` LogLevel *int32 `json:"LogLevel"`
InterceptUnknown bool `json:"InterceptUnknown"` InterceptUnknown bool `json:"InterceptUnknown"`
LogUTC bool `json:"LogUTC"` LogUTC bool `json:"LogUTC"`

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/evilsocket/opensnitch/daemon/firewall"
"github.com/evilsocket/opensnitch/daemon/log" "github.com/evilsocket/opensnitch/daemon/log"
"github.com/evilsocket/opensnitch/daemon/procmon/monitor" "github.com/evilsocket/opensnitch/daemon/procmon/monitor"
"github.com/evilsocket/opensnitch/daemon/rule" "github.com/evilsocket/opensnitch/daemon/rule"
@ -60,6 +61,11 @@ func (c *Client) loadDiskConfiguration(reload bool) {
} }
if reload { if reload {
firewall.Reload(
clientConfig.Firewall,
clientConfig.FwOptions.ConfigPath,
clientConfig.FwOptions.MonitorInterval,
)
return return
} }

View file

@ -110,7 +110,11 @@ func (c *Client) handleActionChangeConfig(stream protocol.UI_NotificationsClient
} }
if c.GetFirewallType() != newConf.Firewall { 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 { if err := monitor.ReconfigureMonitorMethod(newConf.ProcMonitorMethod); err != nil {