allow to configure what firewall to use

Before this change, we tried to determine what firewall to use based on
the version of iptables (if -V legacy -> nftables, otherwise iptables).

This caused problems (#455), and as there's no support yet for nftables
system firewall rules, it can't be configured to workaround these
errors.

Now the default firewall to use will be iptables.
If it's not available (installed), can't be used or the configuration
option is empty/missing, we'll use nftables.
This commit is contained in:
Gustavo Iñiguez Goia 2021-08-09 00:21:43 +02:00
parent db18b0cc63
commit ba7c4e1878
10 changed files with 127 additions and 54 deletions

View file

@ -8,5 +8,6 @@
"DefaultDuration": "once",
"InterceptUnknown": false,
"ProcMonitorMethod": "proc",
"LogLevel": 2
"LogLevel": 2,
"Firewall": "iptables"
}

View file

@ -19,7 +19,7 @@ type (
// Common holds common fields and functionality of both firewalls,
// iptables and nftables.
Common struct {
sync.Mutex
sync.RWMutex
QueueNum uint16
Running bool
RulesChecker *time.Ticker
@ -58,6 +58,9 @@ func (c *Common) SetQueueNum(qNum *int) {
// IsRunning returns if the firewall is running or not.
func (c *Common) IsRunning() bool {
c.RLock()
defer c.RUnlock()
return c != nil && c.Running
}

View file

@ -1,13 +1,10 @@
package iptables
import (
"fmt"
"os/exec"
"regexp"
"strings"
"sync"
"github.com/evilsocket/opensnitch/daemon/core"
"github.com/evilsocket/opensnitch/daemon/firewall/common"
"github.com/evilsocket/opensnitch/daemon/firewall/config"
"github.com/evilsocket/opensnitch/daemon/log"
@ -16,6 +13,13 @@ import (
// Action is the modifier we apply to a rule.
type Action string
const (
// Name is the name that identifies this firewall
Name = "iptables"
// SystemRulePrefix prefix added to each system rule
SystemRulePrefix = "opensnitch-filter"
)
// Actions we apply to the firewall.
const (
ADD = Action("-A")
@ -24,8 +28,6 @@ const (
FLUSH = Action("-F")
NEWCHAIN = Action("-N")
DELCHAIN = Action("-X")
SystemRulePrefix = "opensnitch-filter"
)
// SystemChains holds the fw rules defined by the user
@ -51,7 +53,7 @@ type Iptables struct {
// Fw initializes a new Iptables object
func Fw() (*Iptables, error) {
if err := IsSupported(); err != nil {
if err := IsAvailable(); err != nil {
return nil, err
}
@ -70,7 +72,7 @@ func Fw() (*Iptables, error) {
// Name returns the firewall name
func (ipt *Iptables) Name() string {
return "iptables"
return Name
}
// Init inserts the firewall rules and starts monitoring for firewall
@ -110,20 +112,12 @@ func (ipt *Iptables) Stop() {
ipt.Running = false
}
// IsSupported checks if iptables is installed in the system and what version it is.
// If it's not installed or if it's nftables, nftables will be used instead.
func IsSupported() error {
raw, err := exec.Command("iptables", []string{"-V"}...).CombinedOutput()
// IsAvailable checks if iptables is installed in the system.
func IsAvailable() error {
_, err := exec.Command("iptables", []string{"-V"}...).CombinedOutput()
if err != nil {
return err
}
version := strings.Split(string(raw), " ")
log.Info("iptables version: %s", version)
// format: iptables v1.8.7 (nf_tables), iptables v1.8.7 (legacy), iptables v1.8.7
if len(version) > 2 && core.Trim(version[2]) == "(nf_tables)" {
return fmt.Errorf("fw, using nftables instead of iptables (%v)", version)
}
return nil
}

View file

@ -17,7 +17,7 @@ func (n *Nft) AreRulesLoaded() bool {
return false
}
for _, r := range rules {
if string(r.UserData) == connsRuleKey {
if string(r.UserData) == fwKey {
nRules++
}
}
@ -35,7 +35,7 @@ func (n *Nft) AreRulesLoaded() bool {
return false
}
for _, r := range rules {
if string(r.UserData) == dnsRuleKey {
if string(r.UserData) == fwKey {
nRules++
}
}
@ -49,7 +49,7 @@ func (n *Nft) AreRulesLoaded() bool {
}
func (n *Nft) reloadRulesCallback() {
log.Important("firewall rules changed, reloading")
log.Important("nftables firewall rules changed, reloading")
n.AddSystemRules()
n.InsertRules()
}

View file

@ -11,16 +11,17 @@ import (
)
const (
mangleTableName = "opensnitch-mangle"
filterTableName = "opensnitch-filter"
// Name is the name that identifies this firewall
Name = "nftables"
mangleTableName = "mangle"
filterTableName = "filter"
// The following chains will be under our own mangle or filter tables.
// There shouldn't be other chains with the same name here.
outputChain = "output"
inputChain = "input"
// key assigned to every fw rule we add, in order to get rules by this key.
fwKey = "opesnitch-key"
dnsRuleKey = fwKey + "-dns"
connsRuleKey = fwKey + "-conns"
fwKey = "opensnitch-key"
)
var (
@ -75,7 +76,7 @@ func Fw() (*Nft, error) {
// Name returns the name of the firewall
func (n *Nft) Name() string {
return "nftables"
return Name
}
// Init inserts the firewall rules and starts monitoring for firewall
@ -118,6 +119,7 @@ func (n *Nft) Stop() {
// InsertRules adds fw rules to intercept connections
func (n *Nft) InsertRules() {
n.delInterceptionRules()
n.addGlobalTables()
n.addGlobalChains()
@ -130,10 +132,7 @@ func (n *Nft) InsertRules() {
// CleanRules deletes the rules we added.
func (n *Nft) CleanRules(logErrors bool) {
n.conn.DelTable(mangleTable)
n.conn.DelTable(mangleTable6)
n.conn.DelTable(filterTable)
n.conn.DelTable(filterTable6)
n.delInterceptionRules()
err := n.conn.Flush()
if err != nil && logErrors {
log.Error("Error cleaning nftables tables: %s", err)

View file

@ -92,7 +92,7 @@ func (n *Nft) QueueDNSResponses(enable bool, logError bool) (error, error) {
},
},
// rule key, to allow get it later by key
UserData: []byte(dnsRuleKey),
UserData: []byte(fwKey),
})
}
// apply changes
@ -139,7 +139,7 @@ func (n *Nft) QueueConnections(enable bool, logError bool) (error, error) {
},
},
// rule key, to allow get it later by key
UserData: []byte(connsRuleKey),
UserData: []byte(fwKey),
})
}
// apply changes
@ -149,3 +149,50 @@ func (n *Nft) QueueConnections(enable bool, logError bool) (error, error) {
return nil, nil
}
func (n *Nft) delInterceptionRules() {
n.delRulesByKey(fwKey)
}
func (n *Nft) delRulesByKey(key string) {
chains, err := n.conn.ListChains()
if err != nil {
log.Warning("nftables, error listing chains: %s", err)
return
}
commit := false
for _, c := range chains {
deletedRules := 0
rules, err := n.conn.GetRule(c.Table, c)
if err != nil {
log.Warning("nftables, error listing rules: %s", err)
continue
}
for _, r := range rules {
if string(r.UserData) == key {
// just passing the rule object doesn't work.
if err := n.conn.DelRule(&nftables.Rule{
Table: c.Table,
Chain: c,
Handle: r.Handle,
}); err != nil {
log.Warning("nftables, error deleting interception rule: %s", err)
continue
}
deletedRules++
commit = true
}
}
if deletedRules == len(rules) {
n.conn.DelTable(c.Table)
}
}
if commit {
if err := n.conn.Flush(); err != nil {
log.Warning("nftables, error applying interception rules: %s", err)
}
}
return
}

View file

@ -28,20 +28,6 @@ type Firewall interface {
var fw Firewall
// Determine as soon as possible what firewall to use.
func init() {
var err error
fw, err = iptables.Fw()
if err != nil {
log.Warning("iptables not available: %s", err)
fw, err = nftables.Fw()
}
if err != nil {
log.Warning("firewall error: %s, not iptables nor nftables are available or are usable. Please, report it on github.", err)
}
}
// IsRunning returns if the firewall is running or not.
func IsRunning() bool {
return fw != nil && fw.IsRunning()
@ -49,20 +35,50 @@ func IsRunning() bool {
// CleanRules deletes the rules we added.
func CleanRules(logErrors bool) {
if fw == nil {
return
}
fw.CleanRules(logErrors)
}
// Stop deletes the firewall rules, allowing network traffic.
func Stop() {
if fw == nil {
return
}
fw.Stop()
}
// Init initializes the firewall and loads firewall rules.
func Init(qNum *int) {
func Init(fwType string, qNum *int) {
var err error
if fwType == iptables.Name {
fw, err = iptables.Fw()
if err != nil {
log.Warning("iptables not available: %s", err)
}
}
// if iptables is not installed, we can add nftables rules directly to the kernel,
// without relying on any binaries.
if fwType == nftables.Name || err != nil {
fw, err = nftables.Fw()
if err != nil {
log.Warning("nftables not available: %s", err)
}
}
if err != nil {
log.Warning("firewall error: %s, not iptables nor nftables are available or are usable. Please, report it on github.", err)
return
}
if fw == nil {
log.Error("firewall not initialized.")
return
}
fw.Stop()
fw.Init(qNum)
log.Info("Using %s firewall", fw.Name())

View file

@ -376,10 +376,11 @@ func main() {
}
repeatPktChan = repeatQueue.Packets()
// queue is ready, run firewall rules
firewall.Init(&queueNum)
uiClient = ui.NewClient(uiSocket, stats, rules)
// queue is ready, run firewall rules
firewall.Init(uiClient.GetFirewallType(), &queueNum)
if overwriteLogging() {
setupLogging()
}

View file

@ -7,6 +7,7 @@ import (
"time"
"github.com/evilsocket/opensnitch/daemon/conman"
"github.com/evilsocket/opensnitch/daemon/firewall/iptables"
"github.com/evilsocket/opensnitch/daemon/log"
"github.com/evilsocket/opensnitch/daemon/rule"
"github.com/evilsocket/opensnitch/daemon/statistics"
@ -43,6 +44,7 @@ type Config struct {
InterceptUnknown bool `json:"InterceptUnknown"`
ProcMonitorMethod string `json:"ProcMonitorMethod"`
LogLevel *uint32 `json:"LogLevel"`
Firewall string `json:"Firewall"`
}
// Client holds the connection information of a client.
@ -105,6 +107,16 @@ func (c *Client) InterceptUnknown() bool {
return config.InterceptUnknown
}
// GetFirewallType returns the firewall to use
func (c *Client) GetFirewallType() string {
config.RLock()
defer config.RUnlock()
if config.Firewall == "" {
return iptables.Name
}
return config.Firewall
}
// DefaultAction returns the default configured action for
func (c *Client) DefaultAction() rule.Action {
isConnected := c.Connected()

View file

@ -208,7 +208,7 @@ func (c *Client) handleNotification(stream protocol.UI_NotificationsClient, noti
case notification.Type == protocol.Action_LOAD_FIREWALL:
log.Info("[notification] starting firewall")
firewall.Init(nil)
firewall.Init(c.GetFirewallType(), nil)
c.sendNotificationReply(stream, notification.Id, "", nil)
case notification.Type == protocol.Action_UNLOAD_FIREWALL: