Skip to content

Commit

Permalink
fw: allow to configure config file/ check interval
Browse files Browse the repository at this point in the history
 - 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.
  • Loading branch information
gustavo-iniguez-goya committed Dec 20, 2023
1 parent e905163 commit 54ac5a3
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 39 deletions.
36 changes: 28 additions & 8 deletions daemon/firewall/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ var (
RestoreChains = true
BackupChains = true
ReloadConf = true

DefaultCheckInterval = 10 * time.Second
RulesCheckerDisabled = "0s"
)

type (
Expand All @@ -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
}
)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
Expand All @@ -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)
}
Expand Down
16 changes: 11 additions & 5 deletions daemon/firewall/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -143,15 +143,21 @@ 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
c.watcher = watcher
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
}

Expand Down
6 changes: 4 additions & 2 deletions daemon/firewall/iptables/iptables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down
8 changes: 5 additions & 3 deletions daemon/firewall/nftables/nftables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down
16 changes: 10 additions & 6 deletions daemon/firewall/nftables/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@ type sysChainsListT struct {
expectedRules int
}

var (
configFile = "./testdata/test-sysfw-conf.json"
)

func TestAddSystemRules(t *testing.T) {
nftest.SkipIfNotPrivileged(t)

conn, newNS := nftest.OpenSystemConn(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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
18 changes: 6 additions & 12 deletions daemon/firewall/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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())
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 12 additions & 1 deletion daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).")
Expand Down Expand Up @@ -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)
}
Expand Down
10 changes: 9 additions & 1 deletion daemon/ui/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
6 changes: 6 additions & 0 deletions daemon/ui/config_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -60,6 +61,11 @@ func (c *Client) loadDiskConfiguration(reload bool) {
}

if reload {
firewall.Reload(
clientConfig.Firewall,
clientConfig.FwOptions.ConfigPath,
clientConfig.FwOptions.MonitorInterval,
)
return
}

Expand Down
6 changes: 5 additions & 1 deletion daemon/ui/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 54ac5a3

Please sign in to comment.