mirror of
https://github.com/evilsocket/opensnitch.git
synced 2025-03-04 00:24:40 +01:00
Allow to configure firewall rules from the GUI (#660)
* Allow to configure firewall rules from the GUI (WIP) New features: - Configure and list system firewall rules from the GUI (nftables). - Configure chains' policies. - Add simple rules to allow incoming ports. - Add simple rules to exclude apps (ports) from being intercepted. This feature is only available for nftables. iptables is still supported, you can add rules to the configuration file and they'll be loaded, but you can't configure them from the GUI. More information: #592
This commit is contained in:
parent
16c95d77fd
commit
d9e0c59158
56 changed files with 6574 additions and 464 deletions
|
@ -9,7 +9,7 @@
|
|||
"InterceptUnknown": false,
|
||||
"ProcMonitorMethod": "ebpf",
|
||||
"LogLevel": 2,
|
||||
"Firewall": "iptables",
|
||||
"Firewall": "nftables",
|
||||
"Stats": {
|
||||
"MaxEvents": 150,
|
||||
"MaxStats": 25
|
||||
|
|
|
@ -12,22 +12,24 @@ type (
|
|||
callbackBool func() bool
|
||||
|
||||
stopChecker struct {
|
||||
sync.RWMutex
|
||||
ch chan bool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// Common holds common fields and functionality of both firewalls,
|
||||
// iptables and nftables.
|
||||
Common struct {
|
||||
sync.RWMutex
|
||||
QueueNum uint16
|
||||
Running bool
|
||||
RulesChecker *time.Ticker
|
||||
stopCheckerChan *stopChecker
|
||||
QueueNum uint16
|
||||
Running bool
|
||||
Intercepting bool
|
||||
FwEnabled bool
|
||||
sync.RWMutex
|
||||
}
|
||||
)
|
||||
|
||||
func (s *stopChecker) exit() chan bool {
|
||||
func (s *stopChecker) exit() <-chan bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
return s.ch
|
||||
|
@ -64,13 +66,35 @@ func (c *Common) IsRunning() bool {
|
|||
return c != nil && c.Running
|
||||
}
|
||||
|
||||
// NewRulesChecker starts monitoring firewall for configuration or rules changes.
|
||||
// IsFirewallEnabled returns if the firewall is running or not.
|
||||
func (c *Common) IsFirewallEnabled() bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c != nil && c.FwEnabled
|
||||
}
|
||||
|
||||
// IsIntercepting returns if the firewall is running or not.
|
||||
func (c *Common) IsIntercepting() bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c != nil && c.Intercepting
|
||||
}
|
||||
|
||||
// NewRulesChecker starts monitoring interception rules.
|
||||
// We expect to have 2 rules loaded: one to intercept DNS responses and another one
|
||||
// to intercept network traffic.
|
||||
func (c *Common) NewRulesChecker(areRulesLoaded callbackBool, reloadRules callback) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if c.stopCheckerChan != nil {
|
||||
c.stopCheckerChan.stop()
|
||||
c.stopCheckerChan = nil
|
||||
}
|
||||
|
||||
c.stopCheckerChan = &stopChecker{ch: make(chan bool, 1)}
|
||||
c.RulesChecker = time.NewTicker(time.Second * 30)
|
||||
c.RulesChecker = time.NewTicker(time.Second * 15)
|
||||
|
||||
go c.startCheckingRules(areRulesLoaded, reloadRules)
|
||||
}
|
||||
|
@ -90,13 +114,22 @@ func (c *Common) startCheckingRules(areRulesLoaded callbackBool, reloadRules cal
|
|||
}
|
||||
|
||||
Exit:
|
||||
log.Info("exit checking iptables rules")
|
||||
log.Info("exit checking firewall rules")
|
||||
}
|
||||
|
||||
// StopCheckingRules stops checking if firewall rules are loaded.
|
||||
func (c *Common) StopCheckingRules() {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if c.RulesChecker != nil {
|
||||
c.RulesChecker.Stop()
|
||||
}
|
||||
c.stopCheckerChan.stop()
|
||||
if c.stopCheckerChan != nil {
|
||||
c.stopCheckerChan.stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Common) reloadCallback(callback func()) {
|
||||
callback()
|
||||
}
|
||||
|
|
|
@ -17,52 +17,120 @@ import (
|
|||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
type callback func()
|
||||
// ExprValues holds the statements' options:
|
||||
// "Name": "ct",
|
||||
// "Values": [
|
||||
// {
|
||||
// "Key": "state",
|
||||
// "Value": "established"
|
||||
// },
|
||||
// {
|
||||
// "Key": "state",
|
||||
// "Value": "related"
|
||||
// }]
|
||||
type ExprValues struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// ExprStatement holds the definition of matches to use against connections.
|
||||
//{
|
||||
// "Op": "!=",
|
||||
// "Name": "tcp",
|
||||
// "Values": [
|
||||
// {
|
||||
// "Key": "dport",
|
||||
// "Value": "443"
|
||||
// }
|
||||
// ]
|
||||
//}
|
||||
type ExprStatement struct {
|
||||
Op string // ==, !=, ... Only one per expression set.
|
||||
Name string // tcp, udp, ct, daddr, log, ...
|
||||
Values []*ExprValues // dport 8000
|
||||
}
|
||||
|
||||
// Expressions holds the array of expressions that create the rules
|
||||
type Expressions struct {
|
||||
Statement *ExprStatement
|
||||
}
|
||||
|
||||
// FwRule holds the fields of a rule
|
||||
type FwRule struct {
|
||||
sync.RWMutex
|
||||
// we need to keep old fields in the struct. Otherwise when receiving a conf from the GUI, the legacy rules would be deleted.
|
||||
Chain string // TODO: deprecated, remove
|
||||
Table string // TODO: deprecated, remove
|
||||
Parameters string // TODO: deprecated: remove
|
||||
|
||||
UUID string
|
||||
Description string
|
||||
Table string
|
||||
Chain string
|
||||
Parameters string
|
||||
Expressions []*Expressions
|
||||
Target string
|
||||
TargetParameters string
|
||||
|
||||
Position uint64
|
||||
Enabled bool
|
||||
|
||||
*sync.RWMutex
|
||||
}
|
||||
|
||||
// FwChain holds the information that defines a firewall chain.
|
||||
// It also contains the firewall table definition that it belongs to.
|
||||
type FwChain struct {
|
||||
// table fields
|
||||
Table string
|
||||
Family string
|
||||
// chain fields
|
||||
Name string
|
||||
Description string
|
||||
Priority string
|
||||
Type string
|
||||
Hook string
|
||||
Policy string
|
||||
Rules []*FwRule
|
||||
}
|
||||
|
||||
// IsInvalid checks if the chain has been correctly configured.
|
||||
func (fc *FwChain) IsInvalid() bool {
|
||||
return fc.Name == "" || fc.Family == "" || fc.Table == ""
|
||||
}
|
||||
|
||||
type rulesList struct {
|
||||
sync.RWMutex
|
||||
|
||||
Rule *FwRule
|
||||
}
|
||||
|
||||
type chainsList struct {
|
||||
Chains []*FwChain
|
||||
Rule *FwRule // TODO: deprecated, remove
|
||||
}
|
||||
|
||||
// SystemConfig holds the list of rules to be added to the system
|
||||
type SystemConfig struct {
|
||||
sync.RWMutex
|
||||
SystemRules []*rulesList
|
||||
SystemRules []*chainsList
|
||||
Version uint32
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// Config holds the functionality to re/load the firewall configuration from disk.
|
||||
// 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
|
||||
|
||||
// subscribe to this channel to receive config reload events
|
||||
ReloadConfChan chan bool
|
||||
|
||||
// preloadCallback is called before reloading the configuration,
|
||||
// in order to delete old fw rules.
|
||||
preloadCallback callback
|
||||
preloadCallback func()
|
||||
// reloadCallback is called after the configuration is written.
|
||||
reloadCallback func()
|
||||
// preload will be called after daemon startup, whilst reload when a modification is performed.
|
||||
}
|
||||
|
||||
// NewSystemFwConfig initializes config fields
|
||||
func (c *Config) NewSystemFwConfig(preLoadCb callback) (*Config, error) {
|
||||
func (c *Config) NewSystemFwConfig(preLoadCb, reLoadCb func()) (*Config, error) {
|
||||
var err error
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
|
@ -76,8 +144,8 @@ func (c *Config) NewSystemFwConfig(preLoadCb callback) (*Config, error) {
|
|||
c.file = "/etc/opensnitchd/system-fw.json"
|
||||
c.monitorExitChan = make(chan bool, 1)
|
||||
c.preloadCallback = preLoadCb
|
||||
c.reloadCallback = reLoadCb
|
||||
c.watcher = watcher
|
||||
c.ReloadConfChan = make(chan bool, 1)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
@ -102,7 +170,7 @@ func (c *Config) LoadDiskConfiguration(reload bool) {
|
|||
}
|
||||
|
||||
if reload {
|
||||
c.ReloadConfChan <- true
|
||||
c.reloadCallback()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -125,8 +193,10 @@ func (c *Config) loadConfiguration(rawConfig []byte) {
|
|||
log.Info("fw configuration loaded")
|
||||
}
|
||||
|
||||
func (c *Config) saveConfiguration(rawConfig string) error {
|
||||
conf, err := json.Marshal([]byte(rawConfig))
|
||||
// SaveConfiguration saves configuration to disk.
|
||||
// This event dispatches a reload of the configuration.
|
||||
func (c *Config) SaveConfiguration(rawConfig string) error {
|
||||
conf, err := json.MarshalIndent([]byte(rawConfig), " ", " ")
|
||||
if err != nil {
|
||||
log.Error("saving json firewall configuration: %s %s", err, conf)
|
||||
return err
|
||||
|
@ -150,10 +220,6 @@ func (c *Config) StopConfigWatcher() {
|
|||
c.monitorExitChan <- true
|
||||
close(c.monitorExitChan)
|
||||
}
|
||||
if c.ReloadConfChan != nil {
|
||||
c.ReloadConfChan <- false // exit
|
||||
close(c.ReloadConfChan)
|
||||
}
|
||||
|
||||
if c.watcher != nil {
|
||||
c.watcher.Remove(c.file)
|
||||
|
@ -178,22 +244,3 @@ Exit:
|
|||
c.monitorExitChan = nil
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// MonitorSystemFw waits for configuration reloads.
|
||||
func (c *Config) MonitorSystemFw(reloadCallback callback) {
|
||||
for {
|
||||
select {
|
||||
case reload := <-c.ReloadConfChan:
|
||||
if reload {
|
||||
reloadCallback()
|
||||
} else {
|
||||
goto Exit
|
||||
}
|
||||
}
|
||||
}
|
||||
Exit:
|
||||
log.Info("iptables, stop monitoring system fw rules")
|
||||
c.Lock()
|
||||
c.ReloadConfChan = nil
|
||||
c.Unlock()
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
package iptables
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/common"
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
)
|
||||
|
||||
// Action is the modifier we apply to a rule.
|
||||
|
@ -28,17 +33,27 @@ const (
|
|||
FLUSH = Action("-F")
|
||||
NEWCHAIN = Action("-N")
|
||||
DELCHAIN = Action("-X")
|
||||
POLICY = Action("-P")
|
||||
|
||||
DROP = Action("DROP")
|
||||
ACCEPT = Action("ACCEPT")
|
||||
)
|
||||
|
||||
// SystemChains holds the fw rules defined by the user
|
||||
// SystemRule blabla
|
||||
type SystemRule struct {
|
||||
Table string
|
||||
Chain string
|
||||
Rule *config.FwRule
|
||||
}
|
||||
|
||||
// SystemChains keeps track of the fw rules that have been added to the system.
|
||||
type SystemChains struct {
|
||||
Rules map[string]*SystemRule
|
||||
sync.RWMutex
|
||||
Rules map[string]config.FwRule
|
||||
}
|
||||
|
||||
// Iptables struct holds the fields of the iptables fw
|
||||
type Iptables struct {
|
||||
sync.Mutex
|
||||
config.Config
|
||||
common.Common
|
||||
|
||||
|
@ -49,6 +64,8 @@ type Iptables struct {
|
|||
regexSystemRulesQuery *regexp.Regexp
|
||||
|
||||
chains SystemChains
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Fw initializes a new Iptables object
|
||||
|
@ -65,7 +82,9 @@ func Fw() (*Iptables, error) {
|
|||
bin6: "ip6tables",
|
||||
regexRulesQuery: reRulesQuery,
|
||||
regexSystemRulesQuery: reSystemRulesQuery,
|
||||
chains: SystemChains{Rules: make(map[string]config.FwRule)},
|
||||
chains: SystemChains{
|
||||
Rules: make(map[string]*SystemRule),
|
||||
},
|
||||
}
|
||||
return ipt, nil
|
||||
}
|
||||
|
@ -84,18 +103,15 @@ func (ipt *Iptables) Init(qNum *int) {
|
|||
ipt.SetQueueNum(qNum)
|
||||
|
||||
// In order to clean up any existing firewall rule before start,
|
||||
// we need to load the fw configuration first.
|
||||
ipt.NewSystemFwConfig(ipt.preloadConfCallback)
|
||||
go ipt.MonitorSystemFw(ipt.AddSystemRules)
|
||||
// we need to load the fw configuration first to know what rules
|
||||
// were configured.
|
||||
ipt.NewSystemFwConfig(ipt.preloadConfCallback, ipt.reloadRulesCallback)
|
||||
ipt.LoadDiskConfiguration(false)
|
||||
|
||||
// start from a clean state
|
||||
ipt.CleanRules(false)
|
||||
ipt.InsertRules()
|
||||
|
||||
ipt.AddSystemRules()
|
||||
// start monitoring firewall rules to intercept network traffic
|
||||
ipt.NewRulesChecker(ipt.AreRulesLoaded, ipt.reloadRulesCallback)
|
||||
ipt.EnableInterception()
|
||||
ipt.AddSystemRules(false)
|
||||
|
||||
ipt.Running = true
|
||||
}
|
||||
|
@ -113,6 +129,7 @@ func (ipt *Iptables) Stop() {
|
|||
}
|
||||
|
||||
// IsAvailable checks if iptables is installed in the system.
|
||||
// If it's not, we'll default to nftables.
|
||||
func IsAvailable() error {
|
||||
_, err := exec.Command("iptables", []string{"-V"}...).CombinedOutput()
|
||||
if err != nil {
|
||||
|
@ -121,18 +138,64 @@ func IsAvailable() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// InsertRules adds fw rules to intercept connections
|
||||
func (ipt *Iptables) InsertRules() {
|
||||
if err4, err6 := ipt.QueueDNSResponses(true, true); err4 != nil || err6 != nil {
|
||||
log.Error("Error while running DNS firewall rule: %s %s", err4, err6)
|
||||
} else if err4, err6 = ipt.QueueConnections(true, true); err4 != nil || err6 != nil {
|
||||
// EnableInterception adds fw rules to intercept connections.
|
||||
func (ipt *Iptables) EnableInterception() {
|
||||
if err4, err6 := ipt.QueueConnections(true, true); err4 != nil || err6 != nil {
|
||||
log.Fatal("Error while running conntrack firewall rule: %s %s", err4, err6)
|
||||
} else if err4, err6 = ipt.QueueDNSResponses(true, true); err4 != nil || err6 != nil {
|
||||
log.Error("Error while running DNS firewall rule: %s %s", err4, err6)
|
||||
}
|
||||
// start monitoring firewall rules to intercept network traffic
|
||||
ipt.NewRulesChecker(ipt.AreRulesLoaded, ipt.reloadRulesCallback)
|
||||
}
|
||||
|
||||
// DisableInterception removes firewall rules to intercept outbound connections.
|
||||
func (ipt *Iptables) DisableInterception(logErrors bool) {
|
||||
ipt.StopCheckingRules()
|
||||
ipt.QueueDNSResponses(false, logErrors)
|
||||
ipt.QueueConnections(false, logErrors)
|
||||
}
|
||||
|
||||
// CleanRules deletes the rules we added.
|
||||
func (ipt *Iptables) CleanRules(logErrors bool) {
|
||||
ipt.QueueDNSResponses(false, logErrors)
|
||||
ipt.QueueConnections(false, logErrors)
|
||||
ipt.DisableInterception(logErrors)
|
||||
ipt.DeleteSystemRules(true, logErrors)
|
||||
}
|
||||
|
||||
// Serialize converts the configuration from json to protobuf
|
||||
func (ipt *Iptables) Serialize() (*protocol.SysFirewall, error) {
|
||||
sysfw := &protocol.SysFirewall{}
|
||||
jun := jsonpb.Unmarshaler{
|
||||
AllowUnknownFields: true,
|
||||
}
|
||||
rawConfig, err := json.Marshal(&ipt.SysConfig)
|
||||
if err != nil {
|
||||
log.Error("nfables.Serialize() struct to string error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
// string to proto
|
||||
if err := jun.Unmarshal(strings.NewReader(string(rawConfig)), sysfw); err != nil {
|
||||
log.Error("nfables.Serialize() string to protobuf error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sysfw, nil
|
||||
}
|
||||
|
||||
// Deserialize converts a protocolbuffer structure to json.
|
||||
func (ipt *Iptables) Deserialize(sysfw *protocol.SysFirewall) ([]byte, error) {
|
||||
jun := jsonpb.Marshaler{
|
||||
OrigName: true,
|
||||
EmitDefaults: false,
|
||||
Indent: " ",
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := jun.Marshal(&b, sysfw); err != nil {
|
||||
log.Error("nfables.Deserialize() error 2: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
|
||||
//return nil, fmt.Errorf("iptables.Deserialize() not implemented")
|
||||
}
|
||||
|
|
|
@ -53,10 +53,16 @@ func (ipt *Iptables) AreRulesLoaded() bool {
|
|||
return result
|
||||
}
|
||||
|
||||
// reloadRulesCallback gets called when the interception rules are not present or after the configuration file changes.
|
||||
func (ipt *Iptables) reloadRulesCallback() {
|
||||
log.Important("firewall rules changed, reloading")
|
||||
ipt.QueueDNSResponses(false, false)
|
||||
ipt.QueueConnections(false, false)
|
||||
ipt.InsertRules()
|
||||
ipt.AddSystemRules()
|
||||
ipt.CleanRules(false)
|
||||
ipt.AddSystemRules(true)
|
||||
ipt.EnableInterception()
|
||||
}
|
||||
|
||||
// preloadConfCallback gets called before the fw configuration is reloaded
|
||||
func (ipt *Iptables) preloadConfCallback() {
|
||||
log.Info("iptables config changed, reloading")
|
||||
ipt.DeleteSystemRules(true, log.GetLogLevel() == log.DEBUG)
|
||||
}
|
||||
|
|
|
@ -54,10 +54,10 @@ func (ipt *Iptables) QueueDNSResponses(enable bool, logError bool) (err4, err6 e
|
|||
}
|
||||
|
||||
// QueueConnections inserts the firewall rule which redirects connections to us.
|
||||
// They are queued until the user denies/accept them, or reaches a timeout.
|
||||
// Connections are queued until the user denies/accept them, or reaches a timeout.
|
||||
// OUTPUT -t mangle -m conntrack --ctstate NEW,RELATED -j NFQUEUE --queue-num 0 --queue-bypass
|
||||
func (ipt *Iptables) QueueConnections(enable bool, logError bool) (error, error) {
|
||||
err4, err6 := ipt.RunRule(INSERT, enable, logError, []string{
|
||||
err4, err6 := ipt.RunRule(ADD, enable, logError, []string{
|
||||
"OUTPUT",
|
||||
"-t", "mangle",
|
||||
"-m", "conntrack",
|
||||
|
|
|
@ -4,26 +4,61 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
)
|
||||
|
||||
// CreateSystemRule creates the custom firewall chains and adds them to the system.
|
||||
func (ipt *Iptables) CreateSystemRule(rule *config.FwRule, logErrors bool) {
|
||||
func (ipt *Iptables) CreateSystemRule(rule *config.FwRule, table, chain, hook string, logErrors bool) bool {
|
||||
ipt.chains.Lock()
|
||||
defer ipt.chains.Unlock()
|
||||
if rule == nil {
|
||||
return
|
||||
return false
|
||||
}
|
||||
if table == "" {
|
||||
table = "filter"
|
||||
}
|
||||
if hook == "" {
|
||||
hook = rule.Chain
|
||||
}
|
||||
|
||||
chainName := SystemRulePrefix + "-" + rule.Chain
|
||||
if _, ok := ipt.chains.Rules[rule.Table+"-"+chainName]; ok {
|
||||
return
|
||||
chainName := SystemRulePrefix + "-" + hook
|
||||
if _, ok := ipt.chains.Rules[table+"-"+chainName]; ok {
|
||||
return false
|
||||
}
|
||||
ipt.RunRule(NEWCHAIN, true, logErrors, []string{chainName, "-t", rule.Table})
|
||||
ipt.RunRule(NEWCHAIN, true, logErrors, []string{chainName, "-t", table})
|
||||
|
||||
// Insert the rule at the top of the chain
|
||||
if err4, err6 := ipt.RunRule(INSERT, true, logErrors, []string{rule.Chain, "-t", rule.Table, "-j", chainName}); err4 == nil && err6 == nil {
|
||||
ipt.chains.Rules[rule.Table+"-"+chainName] = *rule
|
||||
if err4, err6 := ipt.RunRule(INSERT, true, logErrors, []string{hook, "-t", table, "-j", chainName}); err4 == nil && err6 == nil {
|
||||
ipt.chains.Rules[table+"-"+chainName] = &SystemRule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Rule: rule,
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
// AddSystemRules creates the system firewall from configuration.
|
||||
func (ipt *Iptables) AddSystemRules(reload bool) {
|
||||
// Version 0 has no Enabled field, so it'd be always false
|
||||
if ipt.SysConfig.Enabled == false && ipt.SysConfig.Version > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, cfg := range ipt.SysConfig.SystemRules {
|
||||
if cfg.Rule != nil {
|
||||
ipt.CreateSystemRule(cfg.Rule, cfg.Rule.Table, cfg.Rule.Chain, cfg.Rule.Chain, true)
|
||||
ipt.AddSystemRule(ADD, cfg.Rule, cfg.Rule.Table, cfg.Rule.Chain, true)
|
||||
continue
|
||||
}
|
||||
|
||||
if cfg.Chains != nil {
|
||||
for _, chn := range cfg.Chains {
|
||||
if chn.Hook != "" && chn.Type != "" {
|
||||
ipt.ConfigureChainPolicy(chn.Type, chn.Hook, chn.Policy, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,34 +69,68 @@ func (ipt *Iptables) DeleteSystemRules(force, logErrors bool) {
|
|||
ipt.chains.Lock()
|
||||
defer ipt.chains.Unlock()
|
||||
|
||||
for _, r := range ipt.SysConfig.SystemRules {
|
||||
if r.Rule == nil {
|
||||
for _, fwCfg := range ipt.SysConfig.SystemRules {
|
||||
if fwCfg.Rule == nil {
|
||||
continue
|
||||
}
|
||||
chain := SystemRulePrefix + "-" + r.Rule.Chain
|
||||
if _, ok := ipt.chains.Rules[r.Rule.Table+"-"+chain]; !ok && !force {
|
||||
chain := SystemRulePrefix + "-" + fwCfg.Rule.Chain
|
||||
if _, ok := ipt.chains.Rules[fwCfg.Rule.Table+"-"+chain]; !ok && !force {
|
||||
continue
|
||||
}
|
||||
ipt.RunRule(FLUSH, true, false, []string{chain, "-t", r.Rule.Table})
|
||||
ipt.RunRule(DELETE, false, logErrors, []string{r.Rule.Chain, "-t", r.Rule.Table, "-j", chain})
|
||||
ipt.RunRule(DELCHAIN, true, false, []string{chain, "-t", r.Rule.Table})
|
||||
delete(ipt.chains.Rules, r.Rule.Table+"-"+chain)
|
||||
ipt.RunRule(FLUSH, true, false, []string{chain, "-t", fwCfg.Rule.Table})
|
||||
ipt.RunRule(DELETE, false, logErrors, []string{fwCfg.Rule.Chain, "-t", fwCfg.Rule.Table, "-j", chain})
|
||||
ipt.RunRule(DELCHAIN, true, false, []string{chain, "-t", fwCfg.Rule.Table})
|
||||
delete(ipt.chains.Rules, fwCfg.Rule.Table+"-"+chain)
|
||||
|
||||
for _, chn := range fwCfg.Chains {
|
||||
if chn.Table == "" {
|
||||
chn.Table = "filter"
|
||||
}
|
||||
chain := SystemRulePrefix + "-" + chn.Hook
|
||||
if _, ok := ipt.chains.Rules[chn.Type+"-"+chain]; !ok && !force {
|
||||
continue
|
||||
}
|
||||
|
||||
ipt.RunRule(FLUSH, true, logErrors, []string{chain, "-t", chn.Type})
|
||||
ipt.RunRule(DELETE, false, logErrors, []string{chn.Hook, "-t", chn.Type, "-j", chain})
|
||||
ipt.RunRule(DELCHAIN, true, logErrors, []string{chain, "-t", chn.Type})
|
||||
delete(ipt.chains.Rules, chn.Type+"-"+chain)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSystemRule deletes a new rule.
|
||||
func (ipt *Iptables) DeleteSystemRule(action Action, rule *config.FwRule, table, chain string, enable bool) (err4, err6 error) {
|
||||
chainName := SystemRulePrefix + "-" + chain
|
||||
if table == "" {
|
||||
table = "filter"
|
||||
}
|
||||
r := []string{chainName, "-t", table}
|
||||
if rule.Parameters != "" {
|
||||
r = append(r, strings.Split(rule.Parameters, " ")...)
|
||||
}
|
||||
r = append(r, []string{"-j", rule.Target}...)
|
||||
if rule.TargetParameters != "" {
|
||||
r = append(r, strings.Split(rule.TargetParameters, " ")...)
|
||||
}
|
||||
|
||||
return ipt.RunRule(action, enable, true, r)
|
||||
}
|
||||
|
||||
// AddSystemRule inserts a new rule.
|
||||
func (ipt *Iptables) AddSystemRule(rule *config.FwRule, enable bool) (err4, err6 error) {
|
||||
func (ipt *Iptables) AddSystemRule(action Action, rule *config.FwRule, table, chain string, enable bool) (err4, err6 error) {
|
||||
if rule == nil {
|
||||
return nil, nil
|
||||
}
|
||||
rule.RLock()
|
||||
defer rule.RUnlock()
|
||||
ipt.RLock()
|
||||
defer ipt.RUnlock()
|
||||
|
||||
chain := SystemRulePrefix + "-" + rule.Chain
|
||||
if rule.Table == "" {
|
||||
rule.Table = "filter"
|
||||
chainName := SystemRulePrefix + "-" + chain
|
||||
if table == "" {
|
||||
table = "filter"
|
||||
}
|
||||
r := []string{chain, "-t", rule.Table}
|
||||
r := []string{chainName, "-t", table}
|
||||
if rule.Parameters != "" {
|
||||
r = append(r, strings.Split(rule.Parameters, " ")...)
|
||||
}
|
||||
|
@ -73,17 +142,13 @@ func (ipt *Iptables) AddSystemRule(rule *config.FwRule, enable bool) (err4, err6
|
|||
return ipt.RunRule(ADD, enable, true, r)
|
||||
}
|
||||
|
||||
// AddSystemRules creates the system firewall from configuration.
|
||||
func (ipt *Iptables) AddSystemRules() {
|
||||
ipt.DeleteSystemRules(true, false)
|
||||
|
||||
for _, r := range ipt.SysConfig.SystemRules {
|
||||
ipt.CreateSystemRule(r.Rule, true)
|
||||
ipt.AddSystemRule(r.Rule, true)
|
||||
}
|
||||
}
|
||||
|
||||
// preloadConfCallback gets called before the fw configuration is reloaded
|
||||
func (ipt *Iptables) preloadConfCallback() {
|
||||
ipt.DeleteSystemRules(true, log.GetLogLevel() == log.DEBUG)
|
||||
// ConfigureChainPolicy configures chains policy.
|
||||
func (ipt *Iptables) ConfigureChainPolicy(table, hook, policy string, logError bool) {
|
||||
// TODO: list all policies before modify them, and restore the original state on exit.
|
||||
// still, if we exit abruptly, we might left the system badly configured.
|
||||
ipt.RunRule(POLICY, true, logError, []string{
|
||||
hook,
|
||||
strings.ToUpper(policy),
|
||||
"-t", table,
|
||||
})
|
||||
}
|
||||
|
|
122
daemon/firewall/nftables/chains.go
Normal file
122
daemon/firewall/nftables/chains.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package nftables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/google/nftables"
|
||||
)
|
||||
|
||||
// AddChain adds a new chain to nftables.
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
|
||||
func (n *Nft) AddChain(name, table, family string, priority nftables.ChainPriority, ctype nftables.ChainType, hook nftables.ChainHook, policy nftables.ChainPolicy) *nftables.Chain {
|
||||
if family == "" {
|
||||
family = exprs.NFT_FAMILY_INET
|
||||
}
|
||||
tbl := getTable(table, family)
|
||||
if tbl == nil {
|
||||
log.Error("%s addChain, Error getting table: %s, %s", logTag, table, family)
|
||||
return nil
|
||||
}
|
||||
|
||||
// nft list chains
|
||||
chain := n.conn.AddChain(&nftables.Chain{
|
||||
Name: strings.ToLower(name),
|
||||
Table: tbl,
|
||||
Type: ctype,
|
||||
Hooknum: hook,
|
||||
Priority: priority,
|
||||
Policy: &policy,
|
||||
})
|
||||
if chain == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
key := getChainKey(name, tbl)
|
||||
sysChains[key] = chain
|
||||
return chain
|
||||
}
|
||||
|
||||
// getChainKey returns the identifier that will be used to link chains and rules.
|
||||
// When adding a new chain the key is stored, then later when adding a rule we get
|
||||
// the chain that the rule belongs to by this key.
|
||||
func getChainKey(name string, table *nftables.Table) string {
|
||||
if table == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s-%s-%d", name, table.Name, table.Family)
|
||||
}
|
||||
|
||||
// get an existing chain
|
||||
func getChain(name string, table *nftables.Table) *nftables.Chain {
|
||||
key := getChainKey(name, table)
|
||||
return sysChains[key]
|
||||
}
|
||||
|
||||
// regular chains are user-defined chains, to better organize fw rules.
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Adding_regular_chains
|
||||
func (n *Nft) addRegularChain(name, table, family string) error {
|
||||
tbl := getTable(table, family)
|
||||
if tbl == nil {
|
||||
return fmt.Errorf("%s addRegularChain, Error getting table: %s, %s", logTag, table, family)
|
||||
}
|
||||
|
||||
chain := n.conn.AddChain(&nftables.Chain{
|
||||
Name: name,
|
||||
Table: tbl,
|
||||
})
|
||||
if chain == nil {
|
||||
return fmt.Errorf("%s error adding regular chain: %s", logTag, name)
|
||||
}
|
||||
key := getChainKey(name, tbl)
|
||||
sysChains[key] = chain
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nft) addInterceptionChains() error {
|
||||
var filterPolicy nftables.ChainPolicy
|
||||
var manglePolicy nftables.ChainPolicy
|
||||
filterPolicy = nftables.ChainPolicyAccept
|
||||
manglePolicy = nftables.ChainPolicyAccept
|
||||
|
||||
tbl := getTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
|
||||
if tbl != nil {
|
||||
key := getChainKey(exprs.NFT_HOOK_INPUT, tbl)
|
||||
if key != "" && sysChains[key] != nil {
|
||||
filterPolicy = *sysChains[key].Policy
|
||||
}
|
||||
}
|
||||
tbl = getTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET)
|
||||
if tbl != nil {
|
||||
key := getChainKey(exprs.NFT_HOOK_OUTPUT, tbl)
|
||||
if key != "" && sysChains[key] != nil {
|
||||
manglePolicy = *sysChains[key].Policy
|
||||
}
|
||||
}
|
||||
|
||||
// nft list tables
|
||||
n.AddChain(exprs.NFT_HOOK_INPUT, exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET,
|
||||
nftables.ChainPriorityFilter, nftables.ChainTypeFilter, nftables.ChainHookInput, filterPolicy)
|
||||
n.AddChain(exprs.NFT_HOOK_OUTPUT, exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET,
|
||||
nftables.ChainPriorityMangle, nftables.ChainTypeRoute, nftables.ChainHookOutput, manglePolicy)
|
||||
|
||||
// apply changes
|
||||
if !n.Commit() {
|
||||
return fmt.Errorf("Error adding interception chains")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nft) delChain(chain *nftables.Chain) error {
|
||||
n.conn.DelChain(chain)
|
||||
delete(sysChains, getChainKey(chain.Name, chain.Table))
|
||||
if !n.Commit() {
|
||||
return fmt.Errorf("delChain, error deleting %s", chain.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
15
daemon/firewall/nftables/exprs/counter.go
Normal file
15
daemon/firewall/nftables/exprs/counter.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// NewExprCounter returns a counter for packets or bytes.
|
||||
func NewExprCounter(counterName string) *[]expr.Any {
|
||||
return &[]expr.Any{
|
||||
&expr.Objref{
|
||||
Type: 1,
|
||||
Name: counterName,
|
||||
},
|
||||
}
|
||||
}
|
85
daemon/firewall/nftables/exprs/ct.go
Normal file
85
daemon/firewall/nftables/exprs/ct.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// Example https://github.com/google/nftables/blob/master/nftables_test.go#L1234
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Setting_packet_metainformation
|
||||
|
||||
// NewExprCtMark returns a new ct expression.
|
||||
// # set
|
||||
// # nft --debug netlink add rule filter output mark set 1
|
||||
// ip filter output
|
||||
// [ immediate reg 1 0x00000001 ]
|
||||
// [ meta set mark with reg 1 ]
|
||||
//
|
||||
// match mark:
|
||||
// nft --debug netlink add rule mangle prerouting ct mark 123
|
||||
// [ ct load mark => reg 1 ]
|
||||
// [ cmp eq reg 1 0x0000007b ]
|
||||
func NewExprCtMark(setMark bool, value string) (*[]expr.Any, error) {
|
||||
mark, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid conntrack mark: %s (%s)", err, value)
|
||||
}
|
||||
|
||||
exprCtMark := []expr.Any{}
|
||||
exprCtMark = append(exprCtMark, []expr.Any{
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(uint32(mark)),
|
||||
},
|
||||
&expr.Ct{
|
||||
Key: expr.CtKeyMARK,
|
||||
Register: 1,
|
||||
SourceRegister: setMark,
|
||||
},
|
||||
}...)
|
||||
if setMark == false {
|
||||
exprCtMark = append(exprCtMark, []expr.Any{
|
||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(uint32(mark))},
|
||||
}...)
|
||||
}
|
||||
|
||||
return &exprCtMark, nil
|
||||
}
|
||||
|
||||
// NewExprCtState returns a new ct expression.
|
||||
func NewExprCtState(ctFlags []*config.ExprValues) (*[]expr.Any, error) {
|
||||
mask := uint32(0)
|
||||
|
||||
for _, flag := range ctFlags {
|
||||
switch strings.ToLower(flag.Value) {
|
||||
case CT_STATE_NEW:
|
||||
mask |= expr.CtStateBitNEW
|
||||
case CT_STATE_ESTABLISHED:
|
||||
mask |= expr.CtStateBitESTABLISHED
|
||||
case CT_STATE_RELATED:
|
||||
mask |= expr.CtStateBitRELATED
|
||||
case CT_STATE_INVALID:
|
||||
mask |= expr.CtStateBitINVALID
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid conntrack flag: %s", flag)
|
||||
}
|
||||
}
|
||||
|
||||
return &[]expr.Any{
|
||||
&expr.Ct{
|
||||
Register: 1, SourceRegister: false, Key: expr.CtKeySTATE,
|
||||
},
|
||||
&expr.Bitwise{
|
||||
SourceRegister: 1,
|
||||
DestRegister: 1,
|
||||
Len: 4,
|
||||
Mask: binaryutil.NativeEndian.PutUint32(mask),
|
||||
Xor: binaryutil.NativeEndian.PutUint32(0),
|
||||
},
|
||||
}, nil
|
||||
}
|
173
daemon/firewall/nftables/exprs/enums.go
Normal file
173
daemon/firewall/nftables/exprs/enums.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package exprs
|
||||
|
||||
// keywords used in the configuration to define rules.
|
||||
const (
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
|
||||
NFT_CHAIN_MANGLE = "mangle"
|
||||
NFT_CHAIN_FILTER = "filter"
|
||||
NFT_CHAIN_RAW = "raw"
|
||||
NFT_CHAIN_SECURITY = "security"
|
||||
NFT_CHAIN_NATDEST = "natdest"
|
||||
NFT_CHAIN_NATSOURCE = "natsource"
|
||||
NFT_CHAIN_CONNTRACK = "conntrack"
|
||||
NFT_CHAIN_SELINUX = "selinux"
|
||||
|
||||
NFT_HOOK_INPUT = "input"
|
||||
NFT_HOOK_OUTPUT = "output"
|
||||
NFT_HOOK_PREROUTING = "prerouting"
|
||||
NFT_HOOK_POSTROUTING = "postrouting"
|
||||
NFT_HOOK_INGRESS = "ingress"
|
||||
NFT_HOOK_EGRESS = "egress"
|
||||
NFT_HOOK_FORWARD = "forward"
|
||||
|
||||
NFT_TABLE_INET = "inet"
|
||||
NFT_TABLE_NAT = "nat"
|
||||
// TODO
|
||||
NFT_TABLE_ARP = "arp"
|
||||
NFT_TABLE_BRIDGE = "bridge"
|
||||
NFT_TABLE_NETDEV = "netdev"
|
||||
|
||||
NFT_FAMILY_IP = "ip"
|
||||
NFT_FAMILY_IP6 = "ip6"
|
||||
NFT_FAMILY_INET = "inet"
|
||||
NFT_FAMILY_BRIDGE = "bridge"
|
||||
NFT_FAMILY_ARP = "arp"
|
||||
NFT_FAMILY_NETDEV = "netdev"
|
||||
|
||||
VERDICT_ACCEPT = "accept"
|
||||
VERDICT_DROP = "drop"
|
||||
VERDICT_REJECT = "reject"
|
||||
VERDICT_RETURN = "return"
|
||||
VERDICT_QUEUE = "queue"
|
||||
|
||||
VERDICT_JUMP = "jump"
|
||||
// TODO
|
||||
VERDICT_GOTO = "goto"
|
||||
VERDICT_STOP = "stop"
|
||||
VERDICT_STOLEN = "stolen"
|
||||
VERDICT_CONTINUE = "continue"
|
||||
VERDICT_MASQUERADE = "masquerade"
|
||||
VERDICT_DNAT = "dnat"
|
||||
VERDICT_SNAT = "snat"
|
||||
VERDICT_REDIRECT = "redirect"
|
||||
VERDICT_TPROXY = "tproxy"
|
||||
|
||||
NFT_PARM_TO = "to"
|
||||
|
||||
NFT_QUEUE_NUM = "num"
|
||||
NFT_QUEUE_BY_PASS = "queue-bypass"
|
||||
|
||||
NFT_MASQ_RANDOM = "random"
|
||||
NFT_MASQ_FULLY_RANDOM = "fully-random"
|
||||
NFT_MASQ_PERSISTENT = "persistent"
|
||||
|
||||
NFT_PROTOCOL = "protocol"
|
||||
NFT_SPORT = "sport"
|
||||
NFT_DPORT = "dport"
|
||||
NFT_SADDR = "saddr"
|
||||
NFT_DADDR = "daddr"
|
||||
NFT_ICMP_CODE = "code"
|
||||
NFT_ICMP_TYPE = "type"
|
||||
|
||||
NFT_IIFNAME = "iifname"
|
||||
NFT_OIFNAME = "oifname"
|
||||
|
||||
NFT_LOG = "log"
|
||||
NFT_LOG_PREFIX = "prefix"
|
||||
// TODO
|
||||
NFT_LOG_LEVEL = "level"
|
||||
NFT_LOG_FLAGS = "flags"
|
||||
|
||||
NFT_CT = "ct"
|
||||
NFT_CT_STATE = "state"
|
||||
NFT_CT_SET_MARK = "set"
|
||||
NFT_CT_MARK = "mark"
|
||||
CT_STATE_NEW = "new"
|
||||
CT_STATE_ESTABLISHED = "established"
|
||||
CT_STATE_RELATED = "related"
|
||||
CT_STATE_INVALID = "invalid"
|
||||
|
||||
NFT_NOTRACK = "notrack"
|
||||
|
||||
NFT_QUOTA = "quota"
|
||||
NFT_QUOTA_UNTIL = "until"
|
||||
NFT_QUOTA_OVER = "over"
|
||||
NFT_QUOTA_USED = "used"
|
||||
NFT_QUOTA_UNIT_BYTES = "bytes"
|
||||
NFT_QUOTA_UNIT_KB = "kbytes"
|
||||
NFT_QUOTA_UNIT_MB = "mbytes"
|
||||
NFT_QUOTA_UNIT_GB = "gbytes"
|
||||
|
||||
NFT_COUNTER = "counter"
|
||||
NFT_COUNTER_NAME = "name"
|
||||
NFT_COUNTER_PACKETS = "packets"
|
||||
NFT_COUNTER_BYTES = "bytes"
|
||||
|
||||
NFT_LIMIT = "limit"
|
||||
NFT_LIMIT_OVER = "over"
|
||||
NFT_LIMIT_BURST = "burst"
|
||||
NFT_LIMIT_UNITS_RATE = "rate-units"
|
||||
NFT_LIMIT_UNITS_TIME = "time-units"
|
||||
NFT_LIMIT_UNITS = "units"
|
||||
NFT_LIMIT_UNIT_SECOND = "second"
|
||||
NFT_LIMIT_UNIT_MINUTE = "minute"
|
||||
NFT_LIMIT_UNIT_HOUR = "hour"
|
||||
NFT_LIMIT_UNIT_DAY = "day"
|
||||
NFT_LIMIT_UNIT_KBYTES = "kbytes"
|
||||
NFT_LIMIT_UNIT_MBYTES = "mbytes"
|
||||
|
||||
NFT_META = "meta"
|
||||
NFT_META_MARK = "mark"
|
||||
NFT_META_SET_MARK = "set"
|
||||
NFT_META_PRIORITY = "priority"
|
||||
NFT_META_NFTRACE = "nftrace"
|
||||
NFT_META_SET = "set"
|
||||
|
||||
NFT_PROTO_UDP = "udp"
|
||||
NFT_PROTO_UDPLITE = "udplite"
|
||||
NFT_PROTO_TCP = "tcp"
|
||||
NFT_PROTO_SCTP = "sctp"
|
||||
NFT_PROTO_DCCP = "dccp"
|
||||
NFT_PROTO_ICMP = "icmp"
|
||||
NFT_PROTO_ICMPX = "icmpx"
|
||||
NFT_PROTO_ICMPv6 = "icmpv6"
|
||||
NFT_PROTO_AH = "ah"
|
||||
NFT_PROTO_ETHERNET = "ethernet"
|
||||
NFT_PROTO_GRE = "gre"
|
||||
NFT_PROTO_IP = "ip"
|
||||
NFT_PROTO_IPIP = "ipip"
|
||||
NFT_PROTO_L2TP = "l2tp"
|
||||
NFT_PROTO_COMP = "comp"
|
||||
NFT_PROTO_IGMP = "igmp"
|
||||
NFT_PROTO_ESP = "esp"
|
||||
NFT_PROTO_RAW = "raw"
|
||||
NFT_PROTO_ENCAP = "encap"
|
||||
|
||||
ICMP_NO_ROUTE = "no-route"
|
||||
ICMP_PROT_UNREACHABLE = "prot-unreachable"
|
||||
ICMP_PORT_UNREACHABLE = "port-unreachable"
|
||||
ICMP_NET_UNREACHABLE = "net-unreachable"
|
||||
ICMP_ADDR_UNREACHABLE = "addr-unreachable"
|
||||
ICMP_HOST_UNREACHABLE = "host-unreachable"
|
||||
ICMP_NET_PROHIBITED = "net-prohibited"
|
||||
ICMP_HOST_PROHIBITED = "host-prohibited"
|
||||
ICMP_ADMIN_PROHIBITED = "admin-prohibited"
|
||||
ICMP_REJECT_ROUTE = "reject-route"
|
||||
ICMP_REJECT_POLICY_FAIL = "policy-fail"
|
||||
|
||||
ICMP_ECHO_REPLY = "echo-reply"
|
||||
ICMP_ECHO_REQUEST = "echo-request"
|
||||
ICMP_SOURCE_QUENCH = "source-quench"
|
||||
ICMP_DEST_UNREACHABLE = "destination-unreachable"
|
||||
ICMP_REDIRECT = "redirect"
|
||||
ICMP_TIME_EXCEEDED = "time-exceeded"
|
||||
ICMP_INFO_REQUEST = "info-request"
|
||||
ICMP_INFO_REPLY = "info-reply"
|
||||
ICMP_PARAMETER_PROBLEM = "parameter-problem"
|
||||
ICMP_TIMESTAMP_REQUEST = "timestamp-request"
|
||||
ICMP_TIMESTAMP_REPLY = "timestamp-reply"
|
||||
ICMP_ROUTER_ADVERTISEMENT = "router-advertisement"
|
||||
ICMP_ROUTER_SOLICITATION = "router-solicitation"
|
||||
ICMP_ADDRESS_MASK_REQUEST = "address-mask-request"
|
||||
ICMP_ADDRESS_MASK_REPLY = "address-mask-reply"
|
||||
)
|
26
daemon/firewall/nftables/exprs/iface.go
Normal file
26
daemon/firewall/nftables/exprs/iface.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package exprs
|
||||
|
||||
import "github.com/google/nftables/expr"
|
||||
|
||||
// NewExprIface returns a new network interface expression
|
||||
func NewExprIface(iface string, isOut bool, cmpOp expr.CmpOp) *[]expr.Any {
|
||||
keyDev := expr.MetaKeyIIFNAME
|
||||
if isOut {
|
||||
keyDev = expr.MetaKeyOIFNAME
|
||||
}
|
||||
return &[]expr.Any{
|
||||
&expr.Meta{Key: keyDev, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: cmpOp,
|
||||
Register: 1,
|
||||
Data: ifname(iface),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/google/nftables/blob/master/nftables_test.go#L81
|
||||
func ifname(n string) []byte {
|
||||
b := make([]byte, 16)
|
||||
copy(b, []byte(n+"\x00"))
|
||||
return b
|
||||
}
|
173
daemon/firewall/nftables/exprs/ip.go
Normal file
173
daemon/firewall/nftables/exprs/ip.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/google/nftables/expr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewExprIP returns a new IP expression.
|
||||
// You can use multiple statements to specify daddr + saddr, or combine them
|
||||
// in a single statement expression:
|
||||
// Example 1 (filtering by source and dest address):
|
||||
// "Name": "ip",
|
||||
// "Values": [ {"Key": "saddr": "Value": "1.2.3.4"},{"Key": "daddr": "Value": "1.2.3.5"} ]
|
||||
// Example 2 (filtering by multiple dest addrs IPs):
|
||||
// "Name": "ip",
|
||||
// "Values": [
|
||||
// {"Key": "daddr": "Value": "1.2.3.4"},
|
||||
// {"Key": "daddr": "Value": "1.2.3.5"}
|
||||
// ]
|
||||
// Example 3 (filtering by network range):
|
||||
// "Name": "ip",
|
||||
// "Values": [
|
||||
// {"Key": "daddr": "Value": "1.2.3.4-1.2.9.254"}
|
||||
// ]
|
||||
// TODO (filter by multiple dest addrs separated by commas):
|
||||
// "Values": [
|
||||
// {"Key": "daddr": "Value": "1.2.3.4,1.2.9.254"}
|
||||
// ]
|
||||
func NewExprIP(ipOptions []*config.ExprValues, cmpOp expr.CmpOp) (*[]expr.Any, error) {
|
||||
var exprIP []expr.Any
|
||||
|
||||
for _, ipOpt := range ipOptions {
|
||||
// TODO: ipv6
|
||||
switch ipOpt.Key {
|
||||
case NFT_SADDR, NFT_DADDR:
|
||||
payload := getExprIPPayload(ipOpt.Key)
|
||||
exprIP = append(exprIP, payload)
|
||||
exprIPtemp, err := getExprIP(ipOpt.Value, cmpOp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exprIP = append(exprIP, *exprIPtemp...)
|
||||
case NFT_PROTOCOL:
|
||||
payload := getExprIPPayload(ipOpt.Key)
|
||||
exprIP = append(exprIP, payload)
|
||||
protoCode, err := getProtocolCode(ipOpt.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exprIP = append(exprIP, []expr.Any{
|
||||
&expr.Cmp{
|
||||
Op: cmpOp,
|
||||
Register: 1,
|
||||
Data: []byte{protoCode},
|
||||
},
|
||||
}...)
|
||||
}
|
||||
}
|
||||
return &exprIP, nil
|
||||
}
|
||||
|
||||
func getExprIPPayload(what string) *expr.Payload {
|
||||
|
||||
switch what {
|
||||
case NFT_PROTOCOL:
|
||||
return &expr.Payload{
|
||||
DestRegister: 1,
|
||||
Offset: 9, // daddr
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Len: 1, // 16 ipv6
|
||||
}
|
||||
case NFT_DADDR:
|
||||
// NOTE 1: if "what" is daddr and SourceRegister is part of the Payload{} expression,
|
||||
// the rule is not added.
|
||||
return &expr.Payload{
|
||||
DestRegister: 1,
|
||||
Offset: 16, // daddr
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Len: 4, // 16 ipv6
|
||||
}
|
||||
|
||||
default:
|
||||
return &expr.Payload{
|
||||
SourceRegister: 1,
|
||||
DestRegister: 1,
|
||||
Offset: 12, // saddr
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Len: 4, // 16 ipv6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Supported IP types: a.b.c.d, a.b.c.d-w.x.y.z
|
||||
// TODO: support IPs separated by commas: a.b.c.d, e.f.g.h,...
|
||||
func getExprIP(value string, cmpOp expr.CmpOp) (*[]expr.Any, error) {
|
||||
if strings.Index(value, "-") != -1 {
|
||||
ips := strings.Split(value, "-")
|
||||
ipSrc := net.ParseIP(ips[0])
|
||||
ipDst := net.ParseIP(ips[1])
|
||||
if ipSrc == nil || ipDst == nil {
|
||||
return nil, fmt.Errorf("Invalid IPs range: %v", ips)
|
||||
}
|
||||
|
||||
return &[]expr.Any{
|
||||
&expr.Range{
|
||||
Op: cmpOp,
|
||||
Register: 1,
|
||||
FromData: ipSrc.To4(),
|
||||
ToData: ipDst.To4(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
ip := net.ParseIP(value)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("Invalid IP: %s", value)
|
||||
}
|
||||
|
||||
return &[]expr.Any{
|
||||
&expr.Cmp{
|
||||
Op: cmpOp,
|
||||
Register: 1,
|
||||
Data: ip.To4(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getProtocolCode(value string) (byte, error) {
|
||||
switch value {
|
||||
case NFT_PROTO_TCP:
|
||||
return unix.IPPROTO_TCP, nil
|
||||
case NFT_PROTO_UDP:
|
||||
return unix.IPPROTO_UDP, nil
|
||||
case NFT_PROTO_UDPLITE:
|
||||
return unix.IPPROTO_UDPLITE, nil
|
||||
case NFT_PROTO_SCTP:
|
||||
return unix.IPPROTO_SCTP, nil
|
||||
case NFT_PROTO_DCCP:
|
||||
return unix.IPPROTO_DCCP, nil
|
||||
case NFT_PROTO_ICMP:
|
||||
return unix.IPPROTO_ICMP, nil
|
||||
case NFT_PROTO_ICMPv6:
|
||||
return unix.IPPROTO_ICMPV6, nil
|
||||
case NFT_PROTO_AH:
|
||||
return unix.IPPROTO_AH, nil
|
||||
case NFT_PROTO_ETHERNET:
|
||||
return unix.IPPROTO_ETHERNET, nil
|
||||
case NFT_PROTO_GRE:
|
||||
return unix.IPPROTO_GRE, nil
|
||||
case NFT_PROTO_IP:
|
||||
return unix.IPPROTO_IP, nil
|
||||
case NFT_PROTO_IPIP:
|
||||
return unix.IPPROTO_IPIP, nil
|
||||
case NFT_PROTO_L2TP:
|
||||
return unix.IPPROTO_L2TP, nil
|
||||
case NFT_PROTO_COMP:
|
||||
return unix.IPPROTO_COMP, nil
|
||||
case NFT_PROTO_IGMP:
|
||||
return unix.IPPROTO_IGMP, nil
|
||||
case NFT_PROTO_ESP:
|
||||
return unix.IPPROTO_ESP, nil
|
||||
case NFT_PROTO_RAW:
|
||||
return unix.IPPROTO_RAW, nil
|
||||
case NFT_PROTO_ENCAP:
|
||||
return unix.IPPROTO_ENCAP, nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("Invalid protocol code, or not supported yet: %s", value)
|
||||
}
|
83
daemon/firewall/nftables/exprs/limit.go
Normal file
83
daemon/firewall/nftables/exprs/limit.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// NewExprLimit returns a new limit expression.
|
||||
// limit rate [over] 1/second
|
||||
// to express bytes units, we use: 10-mbytes instead of nft's 10 mbytes
|
||||
func NewExprLimit(statement *config.ExprStatement) (*[]expr.Any, error) {
|
||||
var err error
|
||||
exprLimit := &expr.Limit{
|
||||
Type: expr.LimitTypePkts,
|
||||
Over: false,
|
||||
Unit: expr.LimitTimeSecond,
|
||||
}
|
||||
|
||||
for _, values := range statement.Values {
|
||||
switch values.Key {
|
||||
|
||||
case NFT_LIMIT_OVER:
|
||||
exprLimit.Over = true
|
||||
|
||||
case NFT_LIMIT_UNITS:
|
||||
exprLimit.Rate, err = strconv.ParseUint(values.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid limit rate: %s", values.Value)
|
||||
}
|
||||
|
||||
case NFT_LIMIT_BURST:
|
||||
limitBurst := 0
|
||||
limitBurst, err = strconv.Atoi(values.Value)
|
||||
if err != nil || limitBurst == 0 {
|
||||
return nil, fmt.Errorf("Invalid burst limit: %s, err: %s", values.Value, err)
|
||||
}
|
||||
exprLimit.Burst = uint32(limitBurst)
|
||||
|
||||
case NFT_LIMIT_UNITS_RATE:
|
||||
// units rate must be placed AFTER the rate
|
||||
exprLimit.Type, exprLimit.Rate = getLimitRate(values.Value, exprLimit.Rate)
|
||||
|
||||
case NFT_LIMIT_UNITS_TIME:
|
||||
exprLimit.Unit = getLimitUnits(values.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return &[]expr.Any{exprLimit}, nil
|
||||
}
|
||||
|
||||
func getLimitUnits(units string) (limitUnits expr.LimitTime) {
|
||||
switch units {
|
||||
case NFT_LIMIT_UNIT_MINUTE:
|
||||
limitUnits = expr.LimitTimeMinute
|
||||
case NFT_LIMIT_UNIT_HOUR:
|
||||
limitUnits = expr.LimitTimeHour
|
||||
case NFT_LIMIT_UNIT_DAY:
|
||||
limitUnits = expr.LimitTimeDay
|
||||
default:
|
||||
limitUnits = expr.LimitTimeSecond
|
||||
}
|
||||
|
||||
return limitUnits
|
||||
}
|
||||
|
||||
func getLimitRate(units string, rate uint64) (limitType expr.LimitType, limitRate uint64) {
|
||||
switch units {
|
||||
case NFT_LIMIT_UNIT_KBYTES:
|
||||
limitRate = rate * 1024
|
||||
limitType = expr.LimitTypePktBytes
|
||||
case NFT_LIMIT_UNIT_MBYTES:
|
||||
limitRate = (rate * 1024) * 1024
|
||||
limitType = expr.LimitTypePktBytes
|
||||
default:
|
||||
limitType = expr.LimitTypePkts
|
||||
limitRate, _ = strconv.ParseUint(units, 10, 64)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
29
daemon/firewall/nftables/exprs/log.go
Normal file
29
daemon/firewall/nftables/exprs/log.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/expr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewExprLog returns a new log expression.
|
||||
func NewExprLog(what, options string) *[]expr.Any {
|
||||
exprLog := []expr.Any{}
|
||||
|
||||
options += " "
|
||||
switch what {
|
||||
case NFT_LOG_PREFIX:
|
||||
exprLog = append(exprLog, []expr.Any{
|
||||
&expr.Log{
|
||||
Key: 1 << unix.NFTA_LOG_PREFIX,
|
||||
Data: []byte(options),
|
||||
},
|
||||
}...)
|
||||
// TODO
|
||||
//case exprs.NFT_LOG_LEVEL:
|
||||
//case exprs.NFT_LOG_FLAGS:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
return &exprLog
|
||||
}
|
97
daemon/firewall/nftables/exprs/meta.go
Normal file
97
daemon/firewall/nftables/exprs/meta.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// NewExprMeta creates a new meta selector to match or set packet metainformation.
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation
|
||||
func NewExprMeta(values []*config.ExprValues) (*[]expr.Any, error) {
|
||||
setValue := false
|
||||
metaExpr := []expr.Any{}
|
||||
|
||||
for _, meta := range values {
|
||||
switch meta.Key {
|
||||
case NFT_META_SET_MARK:
|
||||
setValue = true
|
||||
continue
|
||||
|
||||
case NFT_META_MARK:
|
||||
mark, err := getMetaValue(meta.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if setValue {
|
||||
metaExpr = append(metaExpr, []expr.Any{
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(uint32(mark)),
|
||||
}}...)
|
||||
}
|
||||
metaExpr = append(metaExpr, []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyMARK, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(uint32(mark)),
|
||||
}}...)
|
||||
return &metaExpr, nil
|
||||
|
||||
case NFT_META_PRIORITY:
|
||||
mark, err := getMetaValue(meta.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if setValue {
|
||||
metaExpr = append(metaExpr, []expr.Any{
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(uint32(mark)),
|
||||
}}...)
|
||||
}
|
||||
return &[]expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyPRIORITY, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(uint32(mark)),
|
||||
},
|
||||
}, nil
|
||||
case NFT_META_NFTRACE:
|
||||
mark, err := getMetaValue(meta.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mark != 0 && mark != 1 {
|
||||
return nil, fmt.Errorf("%s Invalid nftrace value: %d. Only 1 or 0 allowed", "nftables", mark)
|
||||
}
|
||||
// TODO: not working yet
|
||||
return &[]expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyNFTRACE, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(uint32(mark)),
|
||||
},
|
||||
}, nil
|
||||
|
||||
default:
|
||||
// not supported yet
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s meta keyword not supported yet, open a new issue on github", "nftables")
|
||||
}
|
||||
|
||||
func getMetaValue(value string) (int, error) {
|
||||
metaVal, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return metaVal, nil
|
||||
}
|
140
daemon/firewall/nftables/exprs/nat.go
Normal file
140
daemon/firewall/nftables/exprs/nat.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/expr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewExprNATFlags returns the nat flags configured.
|
||||
// common to masquerade, snat and dnat
|
||||
func NewExprNATFlags(parms string) (random, fullrandom, persistent bool) {
|
||||
masqParms := strings.Split(parms, ",")
|
||||
for _, mParm := range masqParms {
|
||||
switch mParm {
|
||||
case NFT_MASQ_RANDOM:
|
||||
random = true
|
||||
case NFT_MASQ_FULLY_RANDOM:
|
||||
fullrandom = true
|
||||
case NFT_MASQ_PERSISTENT:
|
||||
persistent = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewExprNAT parses the redirection of redirect, snat, dnat, tproxy and masquerade verdict:
|
||||
// to x.y.z.a:abcd
|
||||
// If only the IP is specified (to 1.2.3.4), only NAT.RegAddrMin must be present (regAddr == true)
|
||||
// If only the port is specified (to :1234), only NAT.RegPortMin must be present (regPort == true)
|
||||
// If both addr and port are specified (to 1.2.3.4:1234), NAT.RegPortMin and NAT.RegAddrMin must be present.
|
||||
func NewExprNAT(parms, verdict string) (bool, bool, *[]expr.Any, error) {
|
||||
regAddr := false
|
||||
regProto := false
|
||||
exprNAT := []expr.Any{}
|
||||
NATParms := strings.Split(parms, " ")
|
||||
|
||||
idx := 0
|
||||
// exclude first parameter if it's "to"
|
||||
if NATParms[idx] == NFT_PARM_TO {
|
||||
idx++
|
||||
}
|
||||
dParms := strings.Split(NATParms[idx], ":")
|
||||
// masquerade doesn't allow "to IP"
|
||||
if dParms[0] != "" && verdict != VERDICT_MASQUERADE {
|
||||
dIP := dParms[0]
|
||||
destIP := net.ParseIP(dIP)
|
||||
if destIP == nil {
|
||||
return regAddr, regProto, &exprNAT, fmt.Errorf("Invalid IP: %s", dIP)
|
||||
}
|
||||
|
||||
exprNAT = append(exprNAT, []expr.Any{
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: destIP.To4(),
|
||||
}}...)
|
||||
regAddr = true
|
||||
}
|
||||
|
||||
if len(dParms) == 2 {
|
||||
dPort := dParms[1]
|
||||
// TODO: support ranges. 9000-9100
|
||||
destPort, err := strconv.Atoi(dPort)
|
||||
if err != nil {
|
||||
return regAddr, regProto, &exprNAT, fmt.Errorf("Invalid Port: %s", dPort)
|
||||
}
|
||||
reg := uint32(2)
|
||||
if verdict == VERDICT_TPROXY || verdict == VERDICT_MASQUERADE || verdict == VERDICT_REDIRECT {
|
||||
reg = 1
|
||||
}
|
||||
exprNAT = append(exprNAT, []expr.Any{
|
||||
&expr.Immediate{
|
||||
Register: reg,
|
||||
Data: binaryutil.BigEndian.PutUint16(uint16(destPort)),
|
||||
}}...)
|
||||
regProto = true
|
||||
}
|
||||
|
||||
return regAddr, regProto, &exprNAT, nil
|
||||
}
|
||||
|
||||
// NewExprMasquerade returns a new masquerade expression.
|
||||
func NewExprMasquerade(toPorts, random, fullRandom, persistent bool) *[]expr.Any {
|
||||
exprMasq := &expr.Masq{
|
||||
ToPorts: toPorts,
|
||||
Random: random,
|
||||
FullyRandom: fullRandom,
|
||||
Persistent: persistent,
|
||||
}
|
||||
if toPorts {
|
||||
exprMasq.RegProtoMin = 1
|
||||
}
|
||||
return &[]expr.Any{
|
||||
exprMasq,
|
||||
}
|
||||
}
|
||||
|
||||
// NewExprRedirect returns a new redirect expression.
|
||||
func NewExprRedirect() *[]expr.Any {
|
||||
return &[]expr.Any{
|
||||
// Redirect is a special case of DNAT where the destination is the current machine
|
||||
&expr.Redir{
|
||||
RegisterProtoMin: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewExprSNAT returns a new snat expression.
|
||||
func NewExprSNAT() *expr.NAT {
|
||||
return &expr.NAT{
|
||||
Type: expr.NATTypeSourceNAT,
|
||||
Family: unix.NFPROTO_IPV4,
|
||||
}
|
||||
}
|
||||
|
||||
// NewExprDNAT returns a new dnat expression.
|
||||
func NewExprDNAT() *expr.NAT {
|
||||
return &expr.NAT{
|
||||
Type: expr.NATTypeDestNAT,
|
||||
Family: unix.NFPROTO_IPV4,
|
||||
}
|
||||
}
|
||||
|
||||
// NewExprTproxy returns a new tproxy expression.
|
||||
// XXX: is "to x.x.x.x:1234" supported by google/nftables lib? or only "to :1234"?
|
||||
// it creates an erronous rule.
|
||||
func NewExprTproxy() *[]expr.Any {
|
||||
return &[]expr.Any{
|
||||
&expr.TProxy{
|
||||
Family: byte(nftables.TableFamilyIPv4),
|
||||
TableFamily: byte(nftables.TableFamilyIPv4),
|
||||
RegPort: 1,
|
||||
}}
|
||||
}
|
9
daemon/firewall/nftables/exprs/notrack.go
Normal file
9
daemon/firewall/nftables/exprs/notrack.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package exprs
|
||||
|
||||
import "github.com/google/nftables/expr"
|
||||
|
||||
func NewNoTrack() *[]expr.Any {
|
||||
return &[]expr.Any{
|
||||
&expr.Notrack{},
|
||||
}
|
||||
}
|
33
daemon/firewall/nftables/exprs/operator.go
Normal file
33
daemon/firewall/nftables/exprs/operator.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// NewOperator translates a string comparator operator to nftables operator
|
||||
func NewOperator(operator string) expr.CmpOp {
|
||||
switch operator {
|
||||
case "!=":
|
||||
return expr.CmpOpNeq
|
||||
case ">":
|
||||
return expr.CmpOpGt
|
||||
case ">=":
|
||||
return expr.CmpOpGte
|
||||
case "<":
|
||||
return expr.CmpOpLt
|
||||
case "<=":
|
||||
return expr.CmpOpLte
|
||||
}
|
||||
|
||||
return expr.CmpOpEq
|
||||
}
|
||||
|
||||
// NewExprOperator returns a new comparator operator
|
||||
func NewExprOperator(op expr.CmpOp) *[]expr.Any {
|
||||
return &[]expr.Any{
|
||||
&expr.Cmp{
|
||||
Register: 1,
|
||||
Op: op,
|
||||
},
|
||||
}
|
||||
}
|
91
daemon/firewall/nftables/exprs/port.go
Normal file
91
daemon/firewall/nftables/exprs/port.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/binaryutil"
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// NewExprPort returns a new port expression with the given matching operator.
|
||||
func NewExprPort(port string, op *expr.CmpOp) *[]expr.Any {
|
||||
eport, _ := strconv.Atoi(port)
|
||||
return &[]expr.Any{
|
||||
&expr.Cmp{
|
||||
Register: 1,
|
||||
Op: *op,
|
||||
Data: binaryutil.BigEndian.PutUint16(uint16(eport))},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NewExprPortRange returns a new port range expression.
|
||||
func NewExprPortRange(sport string) *[]expr.Any {
|
||||
ports := strings.Split(sport, "-")
|
||||
iport, _ := strconv.Atoi(ports[0])
|
||||
eport, _ := strconv.Atoi(ports[1])
|
||||
return &[]expr.Any{
|
||||
&expr.Cmp{
|
||||
Register: 1,
|
||||
Op: expr.CmpOpGte,
|
||||
Data: binaryutil.BigEndian.PutUint16(uint16(iport))},
|
||||
&expr.Cmp{
|
||||
Register: 1,
|
||||
Op: expr.CmpOpLte,
|
||||
Data: binaryutil.BigEndian.PutUint16(uint16(eport))},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NewExprPortSet returns a new set of ports.
|
||||
func NewExprPortSet(portv string) *[]nftables.SetElement {
|
||||
setElements := []nftables.SetElement{}
|
||||
ports := strings.Split(portv, ",")
|
||||
for _, portv := range ports {
|
||||
portExpr := exprPortSubSet(portv)
|
||||
if portExpr != nil {
|
||||
setElements = append(setElements, *portExpr...)
|
||||
}
|
||||
}
|
||||
|
||||
return &setElements
|
||||
}
|
||||
|
||||
func exprPortSubSet(portv string) *[]nftables.SetElement {
|
||||
port, err := strconv.Atoi(portv)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &[]nftables.SetElement{
|
||||
{Key: binaryutil.BigEndian.PutUint16(uint16(port))},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NewExprPortDirection returns a new expression to match connections based on
|
||||
// the direction of the connection (source, dest)
|
||||
func NewExprPortDirection(direction string) (*expr.Payload, error) {
|
||||
switch direction {
|
||||
case NFT_DPORT:
|
||||
return &expr.Payload{
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseTransportHeader,
|
||||
Offset: 2,
|
||||
Len: 2,
|
||||
}, nil
|
||||
case NFT_SPORT:
|
||||
return &expr.Payload{
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseTransportHeader,
|
||||
Offset: 0,
|
||||
Len: 2,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("Not valid protocol direction: %s", direction)
|
||||
}
|
||||
|
||||
}
|
78
daemon/firewall/nftables/exprs/protocol.go
Normal file
78
daemon/firewall/nftables/exprs/protocol.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/nftables/expr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewExprProtocol creates a new expression to filter connections by protocol
|
||||
func NewExprProtocol(proto string) (*[]expr.Any, error) {
|
||||
switch strings.ToLower(proto) {
|
||||
case NFT_PROTO_UDP:
|
||||
return &[]expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_UDP},
|
||||
},
|
||||
}, nil
|
||||
|
||||
case NFT_PROTO_TCP:
|
||||
return &[]expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_TCP},
|
||||
},
|
||||
}, nil
|
||||
|
||||
case NFT_PROTO_UDPLITE:
|
||||
return &[]expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_UDPLITE},
|
||||
},
|
||||
}, nil
|
||||
|
||||
case NFT_PROTO_SCTP:
|
||||
return &[]expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_SCTP},
|
||||
},
|
||||
}, nil
|
||||
|
||||
case NFT_PROTO_DCCP:
|
||||
return &[]expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_DCCP},
|
||||
},
|
||||
}, nil
|
||||
|
||||
case NFT_PROTO_ICMP:
|
||||
return &[]expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_ICMP},
|
||||
},
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("Not valid protocol rule, invalid or not supported protocol: %s", proto)
|
||||
}
|
||||
|
||||
}
|
64
daemon/firewall/nftables/exprs/quota.go
Normal file
64
daemon/firewall/nftables/exprs/quota.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// NewQuota returns a new quota expression.
|
||||
// TODO: named quotas
|
||||
func NewQuota(opts []*config.ExprValues) (*[]expr.Any, error) {
|
||||
over := false
|
||||
bytes := int64(0)
|
||||
used := int64(0)
|
||||
for _, opt := range opts {
|
||||
switch opt.Key {
|
||||
case NFT_QUOTA_OVER:
|
||||
over = true
|
||||
case NFT_QUOTA_UNIT_BYTES:
|
||||
b, err := strconv.ParseInt(opt.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value)
|
||||
}
|
||||
bytes = b
|
||||
case NFT_QUOTA_USED:
|
||||
// TODO: support for other size units
|
||||
b, err := strconv.ParseInt(opt.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid quota initial consumed bytes: %s", opt.Value)
|
||||
}
|
||||
used = b
|
||||
case NFT_QUOTA_UNIT_KB:
|
||||
b, err := strconv.ParseInt(opt.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value)
|
||||
}
|
||||
bytes = b * 1024
|
||||
case NFT_QUOTA_UNIT_MB:
|
||||
b, err := strconv.ParseInt(opt.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value)
|
||||
}
|
||||
bytes = (b * 1024) * 1024
|
||||
case NFT_QUOTA_UNIT_GB:
|
||||
b, err := strconv.ParseInt(opt.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value)
|
||||
}
|
||||
bytes = ((b * 1024) * 1024) * 1024
|
||||
}
|
||||
}
|
||||
if bytes == 0 {
|
||||
return nil, fmt.Errorf("quota bytes cannot be 0")
|
||||
}
|
||||
return &[]expr.Any{
|
||||
&expr.Quota{
|
||||
Bytes: uint64(bytes),
|
||||
Consumed: uint64(used),
|
||||
Over: over,
|
||||
},
|
||||
}, nil
|
||||
}
|
96
daemon/firewall/nftables/exprs/utils.go
Normal file
96
daemon/firewall/nftables/exprs/utils.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getICMPRejectCode(reason string) uint8 {
|
||||
switch reason {
|
||||
case ICMP_HOST_UNREACHABLE, ICMP_ADDR_UNREACHABLE:
|
||||
return layers.ICMPv4CodeHost
|
||||
case ICMP_PROT_UNREACHABLE:
|
||||
return layers.ICMPv4CodeProtocol
|
||||
case ICMP_PORT_UNREACHABLE:
|
||||
return layers.ICMPv4CodePort
|
||||
case ICMP_ADMIN_PROHIBITED:
|
||||
return layers.ICMPv4CodeCommAdminProhibited
|
||||
case ICMP_HOST_PROHIBITED:
|
||||
return layers.ICMPv4CodeHostAdminProhibited
|
||||
case ICMP_NET_PROHIBITED:
|
||||
return layers.ICMPv4CodeNetAdminProhibited
|
||||
}
|
||||
|
||||
return layers.ICMPv4CodeNet
|
||||
}
|
||||
|
||||
func getICMPxRejectCode(reason string) uint8 {
|
||||
// https://github.com/torvalds/linux/blob/master/net/netfilter/nft_reject.c#L96
|
||||
// https://github.com/google/gopacket/blob/3aa782ce48d4a525acaebab344cedabfb561f870/layers/icmp4.go#L37
|
||||
switch reason {
|
||||
case ICMP_HOST_UNREACHABLE, ICMP_NET_UNREACHABLE:
|
||||
return unix.NFT_REJECT_ICMP_UNREACH // results in -> net-unreachable???
|
||||
case ICMP_PROT_UNREACHABLE:
|
||||
return unix.NFT_REJECT_ICMPX_HOST_UNREACH // results in -> prot-unreachable???
|
||||
case ICMP_PORT_UNREACHABLE:
|
||||
return unix.NFT_REJECT_ICMPX_PORT_UNREACH // results in -> host-unreachable???
|
||||
case ICMP_NO_ROUTE:
|
||||
return unix.NFT_REJECT_ICMPX_NO_ROUTE // results in -> net-unreachable
|
||||
}
|
||||
|
||||
return unix.NFT_REJECT_ICMP_UNREACH // results in -> net-unreachable???
|
||||
}
|
||||
|
||||
// GetICMPType returns an ICMP type code
|
||||
func GetICMPType(icmpType string) uint8 {
|
||||
switch icmpType {
|
||||
case ICMP_ECHO_REPLY:
|
||||
return layers.ICMPv4TypeEchoReply
|
||||
case ICMP_ECHO_REQUEST:
|
||||
return layers.ICMPv4TypeEchoRequest
|
||||
case ICMP_SOURCE_QUENCH:
|
||||
return layers.ICMPv4TypeSourceQuench
|
||||
case ICMP_DEST_UNREACHABLE:
|
||||
return layers.ICMPv4TypeDestinationUnreachable
|
||||
case ICMP_ROUTER_ADVERTISEMENT:
|
||||
return layers.ICMPv4TypeRouterAdvertisement
|
||||
case ICMP_ROUTER_SOLICITATION:
|
||||
return layers.ICMPv4TypeRouterSolicitation
|
||||
case ICMP_REDIRECT:
|
||||
return layers.ICMPv4TypeRedirect
|
||||
case ICMP_TIME_EXCEEDED:
|
||||
return layers.ICMPv4TypeTimeExceeded
|
||||
case ICMP_INFO_REQUEST:
|
||||
return layers.ICMPv4TypeInfoRequest
|
||||
case ICMP_INFO_REPLY:
|
||||
return layers.ICMPv4TypeInfoReply
|
||||
case ICMP_PARAMETER_PROBLEM:
|
||||
return layers.ICMPv4TypeParameterProblem
|
||||
case ICMP_TIMESTAMP_REQUEST:
|
||||
return layers.ICMPv4TypeTimestampRequest
|
||||
case ICMP_TIMESTAMP_REPLY:
|
||||
return layers.ICMPv4TypeTimestampReply
|
||||
case ICMP_ADDRESS_MASK_REQUEST:
|
||||
return layers.ICMPv4TypeAddressMaskRequest
|
||||
case ICMP_ADDRESS_MASK_REPLY:
|
||||
return layers.ICMPv4TypeAddressMaskReply
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getICMPv6RejectCode(reason string) uint8 {
|
||||
switch reason {
|
||||
case ICMP_HOST_UNREACHABLE, ICMP_NET_UNREACHABLE, ICMP_NO_ROUTE:
|
||||
return layers.ICMPv6CodeNoRouteToDst
|
||||
case ICMP_ADDR_UNREACHABLE:
|
||||
return layers.ICMPv6CodeAddressUnreachable
|
||||
case ICMP_PORT_UNREACHABLE:
|
||||
return layers.ICMPv6CodePortUnreachable
|
||||
case ICMP_REJECT_POLICY_FAIL:
|
||||
return layers.ICMPv6CodeSrcAddressFailedPolicy
|
||||
case ICMP_REJECT_ROUTE:
|
||||
return layers.ICMPv6CodeRejectRouteToDst
|
||||
}
|
||||
|
||||
return layers.ICMPv6CodeNoRouteToDst
|
||||
}
|
176
daemon/firewall/nftables/exprs/verdict.go
Normal file
176
daemon/firewall/nftables/exprs/verdict.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
package exprs
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/nftables/expr"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewExprVerdict constructs a new verdict to apply on connections.
|
||||
func NewExprVerdict(verdict, parms string) *[]expr.Any {
|
||||
switch strings.ToLower(verdict) {
|
||||
case VERDICT_ACCEPT:
|
||||
return NewExprAccept()
|
||||
|
||||
case VERDICT_DROP:
|
||||
return &[]expr.Any{&expr.Verdict{
|
||||
Kind: expr.VerdictDrop,
|
||||
}}
|
||||
|
||||
// FIXME: this verdict is not added to nftables
|
||||
case VERDICT_STOP:
|
||||
return &[]expr.Any{&expr.Verdict{
|
||||
Kind: expr.VerdictStop,
|
||||
}}
|
||||
|
||||
case VERDICT_REJECT:
|
||||
reject := NewExprReject(parms)
|
||||
return &[]expr.Any{reject}
|
||||
|
||||
case VERDICT_RETURN:
|
||||
return &[]expr.Any{&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
}}
|
||||
|
||||
case VERDICT_JUMP:
|
||||
return &[]expr.Any{
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictKind(unix.NFT_JUMP),
|
||||
Chain: parms,
|
||||
},
|
||||
}
|
||||
|
||||
case VERDICT_QUEUE:
|
||||
queueNum := 0
|
||||
p := strings.Split(parms, " ")
|
||||
if len(p) > 0 {
|
||||
if p[0] == NFT_QUEUE_NUM {
|
||||
queueNum, _ = strconv.Atoi(p[len(p)-1])
|
||||
}
|
||||
}
|
||||
return &[]expr.Any{
|
||||
&expr.Queue{
|
||||
Num: uint16(queueNum),
|
||||
// TODO: allow to configure this flag
|
||||
Flag: expr.QueueFlagBypass,
|
||||
}}
|
||||
|
||||
case VERDICT_SNAT:
|
||||
snat := NewExprSNAT()
|
||||
snat.Random, snat.FullyRandom, snat.Persistent = NewExprNATFlags(parms)
|
||||
snatExpr := &[]expr.Any{snat}
|
||||
|
||||
if regAddr, regProto, natParms, err := NewExprNAT(parms, VERDICT_SNAT); err == nil {
|
||||
if regAddr {
|
||||
snat.RegAddrMin = 1
|
||||
}
|
||||
if regProto {
|
||||
snat.RegProtoMin = 2
|
||||
}
|
||||
*snatExpr = append(*natParms, *snatExpr...)
|
||||
}
|
||||
return snatExpr
|
||||
|
||||
case VERDICT_DNAT:
|
||||
dnat := NewExprDNAT()
|
||||
dnat.Random, dnat.FullyRandom, dnat.Persistent = NewExprNATFlags(parms)
|
||||
dnatExpr := &[]expr.Any{dnat}
|
||||
|
||||
if regAddr, regProto, natParms, err := NewExprNAT(parms, VERDICT_DNAT); err == nil {
|
||||
if regAddr {
|
||||
dnat.RegAddrMin = 1
|
||||
}
|
||||
if regProto {
|
||||
dnat.RegProtoMin = 2
|
||||
}
|
||||
*dnatExpr = append(*natParms, *dnatExpr...)
|
||||
}
|
||||
return dnatExpr
|
||||
|
||||
case VERDICT_MASQUERADE:
|
||||
m := &expr.Masq{}
|
||||
m.Random, m.FullyRandom, m.Persistent = NewExprNATFlags(parms)
|
||||
masqExpr := &[]expr.Any{m}
|
||||
|
||||
if parms == "" {
|
||||
return masqExpr
|
||||
}
|
||||
// if any of the flag is set to true, toPorts must be false
|
||||
toPorts := !(m.Random == true || m.FullyRandom == true || m.Persistent == true)
|
||||
masqExpr = NewExprMasquerade(toPorts, m.Random, m.FullyRandom, m.Persistent)
|
||||
if _, _, natParms, err := NewExprNAT(parms, VERDICT_MASQUERADE); err == nil {
|
||||
*masqExpr = append(*natParms, *masqExpr...)
|
||||
}
|
||||
|
||||
return masqExpr
|
||||
|
||||
case VERDICT_REDIRECT:
|
||||
if _, _, rewriteParms, err := NewExprNAT(parms, VERDICT_REDIRECT); err == nil {
|
||||
redirExpr := NewExprRedirect()
|
||||
*redirExpr = append(*rewriteParms, *redirExpr...)
|
||||
return redirExpr
|
||||
}
|
||||
|
||||
case VERDICT_TPROXY:
|
||||
if _, _, rewriteParms, err := NewExprNAT(parms, VERDICT_TPROXY); err == nil {
|
||||
tproxyExpr := &[]expr.Any{}
|
||||
*tproxyExpr = append(*tproxyExpr, *rewriteParms...)
|
||||
tVerdict := NewExprTproxy()
|
||||
*tproxyExpr = append(*tproxyExpr, *tVerdict...)
|
||||
*tproxyExpr = append(*tproxyExpr, *NewExprAccept()...)
|
||||
return tproxyExpr
|
||||
}
|
||||
}
|
||||
|
||||
// target can be empty, "ct set mark" or "log" for example
|
||||
return &[]expr.Any{}
|
||||
}
|
||||
|
||||
// NewExprAccept creates the accept verdict.
|
||||
func NewExprAccept() *[]expr.Any {
|
||||
return &[]expr.Any{&expr.Verdict{
|
||||
Kind: expr.VerdictAccept,
|
||||
}}
|
||||
}
|
||||
|
||||
// NewExprReject creates new Reject expression
|
||||
// icmpx, to reject the IPv4 and IPv6 traffic, icmp for ipv4, icmpv6 for ...
|
||||
// Ex.: "Target": "reject", "TargetParameters": "with tcp reset"
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Rejecting_traffic
|
||||
func NewExprReject(parms string) *expr.Reject {
|
||||
reject := &expr.Reject{}
|
||||
reject.Code = unix.NFT_REJECT_ICMP_UNREACH
|
||||
reject.Type = unix.NFT_REJECT_ICMP_UNREACH
|
||||
|
||||
parmList := strings.Split(parms, " ")
|
||||
length := len(parmList)
|
||||
if length <= 1 {
|
||||
return reject
|
||||
}
|
||||
what := parmList[1]
|
||||
how := parmList[length-1]
|
||||
|
||||
switch what {
|
||||
case NFT_PROTO_TCP:
|
||||
reject.Type = unix.NFT_REJECT_TCP_RST
|
||||
reject.Code = unix.NFT_REJECT_TCP_RST
|
||||
case NFT_PROTO_ICMP:
|
||||
reject.Type = unix.NFT_REJECT_ICMP_UNREACH
|
||||
reject.Code = getICMPRejectCode(how)
|
||||
return reject
|
||||
case NFT_PROTO_ICMPX:
|
||||
// icmp and icmpv6
|
||||
reject.Type = unix.NFT_REJECT_ICMPX_UNREACH
|
||||
reject.Code = getICMPxRejectCode(how)
|
||||
return reject
|
||||
case NFT_PROTO_ICMPv6:
|
||||
reject.Type = 1
|
||||
reject.Code = getICMPv6RejectCode(how)
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
return reject
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
package nftables
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
)
|
||||
|
||||
|
@ -10,36 +13,30 @@ func (n *Nft) AreRulesLoaded() bool {
|
|||
defer n.Unlock()
|
||||
|
||||
nRules := 0
|
||||
for _, table := range n.mangleTables {
|
||||
rules, err := n.conn.GetRule(table, n.outputChains[table])
|
||||
if err != nil {
|
||||
log.Error("nftables mangle rules error: %s, %s", table.Name, n.outputChains[table].Name)
|
||||
return false
|
||||
}
|
||||
for _, r := range rules {
|
||||
if string(r.UserData) == fwKey {
|
||||
nRules++
|
||||
}
|
||||
}
|
||||
}
|
||||
if nRules != 2 {
|
||||
log.Warning("nftables mangle rules not loaded: %d", nRules)
|
||||
chains, err := n.conn.ListChains()
|
||||
if err != nil {
|
||||
log.Warning("[nftables] error listing nftables chains: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
nRules = 0
|
||||
for _, table := range n.filterTables {
|
||||
rules, err := n.conn.GetRule(table, n.inputChains[table])
|
||||
for _, c := range chains {
|
||||
rules, err := n.conn.GetRule(c.Table, c)
|
||||
if err != nil {
|
||||
log.Error("nftables filter rules error: %s, %s", table.Name, n.inputChains[table].Name)
|
||||
return false
|
||||
log.Warning("[nftables] Error listing rules: %s", err)
|
||||
continue
|
||||
}
|
||||
for _, r := range rules {
|
||||
if string(r.UserData) == fwKey {
|
||||
for rdx, r := range rules {
|
||||
if string(r.UserData) == interceptionRuleKey {
|
||||
nRules++
|
||||
if c.Table.Name == exprs.NFT_CHAIN_MANGLE && rdx+1 != len(rules) {
|
||||
log.Warning("nfables queue rule is not the latest of the list, reloading")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// we expect to have exactly 2 rules (queue and dns). If there're less or more, then we
|
||||
// need to reload them.
|
||||
if nRules != 2 {
|
||||
log.Warning("nfables filter rules not loaded: %d", nRules)
|
||||
return false
|
||||
|
@ -48,8 +45,23 @@ func (n *Nft) AreRulesLoaded() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// reloadConfCallback gets called after the configuration changes.
|
||||
func (n *Nft) reloadConfCallback() {
|
||||
log.Important("reloadConfCallback changed, reloading")
|
||||
n.DeleteSystemRules(false, log.GetLogLevel() == log.DEBUG)
|
||||
n.AddSystemRules(true)
|
||||
}
|
||||
|
||||
// reloadRulesCallback gets called when the interception rules are not present.
|
||||
func (n *Nft) reloadRulesCallback() {
|
||||
log.Important("nftables firewall rules changed, reloading")
|
||||
n.AddSystemRules()
|
||||
n.InsertRules()
|
||||
n.DisableInterception(true)
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
n.EnableInterception()
|
||||
}
|
||||
|
||||
// preloadConfCallback gets called before the fw configuration is loaded
|
||||
func (n *Nft) preloadConfCallback() {
|
||||
log.Info("nftables config changed, reloading")
|
||||
n.DeleteSystemRules(false, log.GetLogLevel() == log.DEBUG)
|
||||
}
|
||||
|
|
|
@ -1,45 +1,41 @@
|
|||
package nftables
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/common"
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/iptables"
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/google/nftables"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name is the name that identifies this firewall
|
||||
Name = "nftables"
|
||||
// Action is the modifier we apply to a rule.
|
||||
type Action string
|
||||
|
||||
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 = "opensnitch-key"
|
||||
// Actions we apply to the firewall.
|
||||
const (
|
||||
fwKey = "opensnitch-key"
|
||||
interceptionRuleKey = fwKey + "-interception"
|
||||
systemRuleKey = fwKey + "-system"
|
||||
Name = "nftables"
|
||||
)
|
||||
|
||||
var (
|
||||
filterTable = &nftables.Table{
|
||||
Family: nftables.TableFamilyIPv4,
|
||||
Name: filterTableName,
|
||||
}
|
||||
filterTable6 = &nftables.Table{
|
||||
Family: nftables.TableFamilyIPv6,
|
||||
Name: filterTableName,
|
||||
Family: nftables.TableFamilyINet,
|
||||
Name: exprs.NFT_CHAIN_FILTER,
|
||||
}
|
||||
|
||||
mangleTable = &nftables.Table{
|
||||
Family: nftables.TableFamilyIPv4,
|
||||
Name: mangleTableName,
|
||||
}
|
||||
mangleTable6 = &nftables.Table{
|
||||
Family: nftables.TableFamilyIPv6,
|
||||
Name: mangleTableName,
|
||||
Family: nftables.TableFamilyINet,
|
||||
Name: exprs.NFT_CHAIN_FILTER,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -49,13 +45,7 @@ type Nft struct {
|
|||
config.Config
|
||||
common.Common
|
||||
|
||||
conn *nftables.Conn
|
||||
|
||||
mangleTables []*nftables.Table
|
||||
filterTables []*nftables.Table
|
||||
outputChains map[*nftables.Table]*nftables.Chain
|
||||
inputChains map[*nftables.Table]*nftables.Chain
|
||||
|
||||
conn *nftables.Conn
|
||||
chains iptables.SystemChains
|
||||
}
|
||||
|
||||
|
@ -67,9 +57,9 @@ func NewNft() *nftables.Conn {
|
|||
// Fw initializes a new nftables object
|
||||
func Fw() (*Nft, error) {
|
||||
n := &Nft{
|
||||
outputChains: make(map[*nftables.Table]*nftables.Chain),
|
||||
inputChains: make(map[*nftables.Table]*nftables.Chain),
|
||||
chains: iptables.SystemChains{Rules: make(map[string]config.FwRule)},
|
||||
chains: iptables.SystemChains{
|
||||
Rules: make(map[string]*iptables.SystemRule),
|
||||
},
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
@ -85,22 +75,22 @@ func (n *Nft) Init(qNum *int) {
|
|||
if n.IsRunning() {
|
||||
return
|
||||
}
|
||||
initMapsStore()
|
||||
n.SetQueueNum(qNum)
|
||||
n.conn = NewNft()
|
||||
|
||||
// In order to clean up any existing firewall rule before start,
|
||||
// we need to load the fw configuration first.
|
||||
n.NewSystemFwConfig(n.preloadConfCallback)
|
||||
go n.MonitorSystemFw(n.AddSystemRules)
|
||||
// we need to load the fw configuration first to know what rules
|
||||
// were configured.
|
||||
n.NewSystemFwConfig(n.preloadConfCallback, n.reloadConfCallback)
|
||||
n.LoadDiskConfiguration(false)
|
||||
|
||||
// start from a clean state
|
||||
n.CleanRules(false)
|
||||
n.AddSystemRules()
|
||||
|
||||
n.InsertRules()
|
||||
// start monitoring firewall rules to intercept network traffic.
|
||||
n.NewRulesChecker(n.AreRulesLoaded, n.reloadRulesCallback)
|
||||
// The daemon may have exited unexpectedly, leaving residual fw rules, so we
|
||||
// need to clean them up to avoid duplicated rules.
|
||||
n.delInterceptionRules()
|
||||
n.AddSystemRules(false)
|
||||
n.EnableInterception()
|
||||
|
||||
n.Running = true
|
||||
}
|
||||
|
@ -117,25 +107,86 @@ func (n *Nft) Stop() {
|
|||
n.Running = false
|
||||
}
|
||||
|
||||
// InsertRules adds fw rules to intercept connections
|
||||
func (n *Nft) InsertRules() {
|
||||
n.delInterceptionRules()
|
||||
n.addGlobalTables()
|
||||
n.addGlobalChains()
|
||||
// EnableInterception adds firewall rules to intercept connections
|
||||
func (n *Nft) EnableInterception() {
|
||||
if err := n.addInterceptionTables(); err != nil {
|
||||
log.Error("Error while adding interception tables: %s", err)
|
||||
return
|
||||
}
|
||||
if err := n.addInterceptionChains(); err != nil {
|
||||
log.Error("Error while adding interception chains: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err, _ := n.QueueDNSResponses(true, true); err != nil {
|
||||
log.Error("Error while Running DNS nftables rule: %s", err)
|
||||
} else if err, _ = n.QueueConnections(true, true); err != nil {
|
||||
log.Fatal("Error while Running conntrack nftables rule: %s", err)
|
||||
log.Error("Error while running DNS nftables rule: %s", err)
|
||||
}
|
||||
if err, _ := n.QueueConnections(true, true); err != nil {
|
||||
log.Error("Error while running conntrack nftables rule: %s", err)
|
||||
}
|
||||
// start monitoring firewall rules to intercept network traffic.
|
||||
n.NewRulesChecker(n.AreRulesLoaded, n.reloadRulesCallback)
|
||||
}
|
||||
|
||||
// DisableInterception removes firewall rules to intercept outbound connections.
|
||||
func (n *Nft) DisableInterception(logErrors bool) {
|
||||
n.delInterceptionRules()
|
||||
n.StopCheckingRules()
|
||||
}
|
||||
|
||||
// CleanRules deletes the rules we added.
|
||||
func (n *Nft) CleanRules(logErrors bool) {
|
||||
n.delInterceptionRules()
|
||||
err := n.conn.Flush()
|
||||
if err != nil && logErrors {
|
||||
log.Error("Error cleaning nftables tables: %s", err)
|
||||
}
|
||||
n.DisableInterception(logErrors)
|
||||
n.DeleteSystemRules(true, logErrors)
|
||||
}
|
||||
|
||||
// Commit applies the queued changes, creating new objects (tables, chains, etc).
|
||||
// You add rules, chains or tables, and after calling to Flush() they're added to the system.
|
||||
// NOTE: it's very important not to call Flush() without queued tasks.
|
||||
func (n *Nft) Commit() bool {
|
||||
if err := n.conn.Flush(); err != nil {
|
||||
log.Warning("%s error applying changes: %s", logTag, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Serialize converts the configuration from json to protobuf
|
||||
func (n *Nft) Serialize() (*protocol.SysFirewall, error) {
|
||||
sysfw := &protocol.SysFirewall{}
|
||||
jun := jsonpb.Unmarshaler{
|
||||
AllowUnknownFields: true,
|
||||
}
|
||||
rawConfig, err := json.Marshal(&n.SysConfig)
|
||||
if err != nil {
|
||||
log.Error("nftables.Serialize() struct to string error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
// string to proto
|
||||
if err := jun.Unmarshal(strings.NewReader(string(rawConfig)), sysfw); err != nil {
|
||||
log.Error("nftables.Serialize() string to protobuf error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sysfw, nil
|
||||
}
|
||||
|
||||
// Deserialize converts a protocolbuffer structure to byte array.
|
||||
func (n *Nft) Deserialize(sysfw *protocol.SysFirewall) ([]byte, error) {
|
||||
jun := jsonpb.Marshaler{
|
||||
OrigName: true,
|
||||
EmitDefaults: true,
|
||||
Indent: " ",
|
||||
}
|
||||
|
||||
// NOTE: '<' and '>' characters are encoded to unicode (\u003c).
|
||||
// This has no effect on adding rules to nftables.
|
||||
// Users can still write "<" if they want to, rules are added ok.
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := jun.Marshal(&b, sysfw); err != nil {
|
||||
log.Error("nfables.Deserialize() error 2: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
|
161
daemon/firewall/nftables/parser.go
Normal file
161
daemon/firewall/nftables/parser.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
package nftables
|
||||
|
||||
import (
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// nftables rules are composed of expressions, for example:
|
||||
// tcp dport 443 ip daddr 192.168.1.1
|
||||
// \-----------/ \------------------/
|
||||
// with these format:
|
||||
// keyword1<SPACE>keyword2<SPACE>value...
|
||||
//
|
||||
// here we parse the expression, and based on keyword1, we build the rule with the given options.
|
||||
//
|
||||
// If the rule has multiple values (tcp dport 80,443,8080), no spaces are allowed,
|
||||
// and the separator is a ",", instead of the format { 80, 443, 8080 }
|
||||
//
|
||||
// In order to debug invalid expressions, or how to build new ones, use the following command:
|
||||
// # nft --debug netlink add rule filter output mark set 1
|
||||
// ip filter output
|
||||
// [ immediate reg 1 0x00000001 ]
|
||||
// [ meta set mark with reg 1 ]
|
||||
//
|
||||
// Debugging added rules:
|
||||
// nft --debug netlink list ruleset
|
||||
//
|
||||
// https://wiki.archlinux.org/title/Nftables#Expressions
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Building_rules_through_expressions
|
||||
func (n *Nft) parseExpression(table, chain, family string, expression *config.Expressions) *[]expr.Any {
|
||||
var exprList []expr.Any
|
||||
cmpOp := exprs.NewOperator(expression.Statement.Op)
|
||||
|
||||
switch expression.Statement.Name {
|
||||
|
||||
case exprs.NFT_CT:
|
||||
exprCt := n.buildConntrackRule(expression.Statement.Values)
|
||||
if exprCt == nil {
|
||||
log.Warning("%s Ct statement error", logTag)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, *exprCt...)
|
||||
|
||||
case exprs.NFT_META:
|
||||
metaExpr, err := exprs.NewExprMeta(expression.Statement.Values)
|
||||
if err != nil {
|
||||
log.Warning("%s meta statement error: %s", logTag, err)
|
||||
return nil
|
||||
}
|
||||
return metaExpr
|
||||
|
||||
// TODO: support iif, oif
|
||||
case exprs.NFT_IIFNAME, exprs.NFT_OIFNAME:
|
||||
isOut := expression.Statement.Name == exprs.NFT_OIFNAME
|
||||
iface := expression.Statement.Values[0].Key
|
||||
if iface == "" {
|
||||
log.Warning("%s network interface statement error: %s", logTag, expression.Statement.Name)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, *exprs.NewExprIface(iface, isOut, cmpOp)...)
|
||||
|
||||
case exprs.NFT_FAMILY_IP, exprs.NFT_FAMILY_IP6:
|
||||
exprIP, err := exprs.NewExprIP(expression.Statement.Values, cmpOp)
|
||||
if err != nil {
|
||||
log.Warning("%s addr statement error: %s", logTag, err)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, *exprIP...)
|
||||
|
||||
case exprs.NFT_PROTO_ICMP, exprs.NFT_PROTO_ICMPv6:
|
||||
exprICMP := n.buildICMPRule(table, family, expression.Statement.Values)
|
||||
if exprICMP == nil {
|
||||
log.Warning("%s icmp statement error", logTag)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, *exprICMP...)
|
||||
|
||||
case exprs.NFT_LOG:
|
||||
defaultLog := "opensnitch"
|
||||
if len(expression.Statement.Values) > 0 {
|
||||
defaultLog = expression.Statement.Values[0].Value
|
||||
}
|
||||
exprLog := exprs.NewExprLog(exprs.NFT_LOG_PREFIX, defaultLog)
|
||||
if exprLog == nil {
|
||||
log.Warning("%s log statement error", logTag)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, *exprLog...)
|
||||
|
||||
case exprs.NFT_LIMIT:
|
||||
exprLimit, err := exprs.NewExprLimit(expression.Statement)
|
||||
if err != nil {
|
||||
log.Warning("%s %s", logTag, err)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, *exprLimit...)
|
||||
|
||||
case exprs.NFT_PROTO_UDP, exprs.NFT_PROTO_TCP, exprs.NFT_PROTO_UDPLITE, exprs.NFT_PROTO_SCTP, exprs.NFT_PROTO_DCCP:
|
||||
exprProto, err := exprs.NewExprProtocol(expression.Statement.Name)
|
||||
if err != nil {
|
||||
log.Warning("%s proto statement error: %s", logTag, err)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, *exprProto...)
|
||||
|
||||
for _, exprValue := range expression.Statement.Values {
|
||||
|
||||
switch exprValue.Key {
|
||||
case exprs.NFT_DPORT, exprs.NFT_SPORT:
|
||||
exprPDir, err := exprs.NewExprPortDirection(exprValue.Key)
|
||||
if err != nil {
|
||||
log.Warning("%s ports statement error: %s", logTag, err)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, []expr.Any{exprPDir}...)
|
||||
exprList = append(exprList, *n.buildProtocolRule(table, family, exprValue.Value, &cmpOp)...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case exprs.NFT_QUOTA:
|
||||
exprQuota, err := exprs.NewQuota(expression.Statement.Values)
|
||||
if err != nil {
|
||||
log.Warning("%s quota statement error: %s", logTag, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
exprList = append(exprList, *exprQuota...)
|
||||
|
||||
case exprs.NFT_NOTRACK:
|
||||
exprList = append(exprList, *exprs.NewNoTrack()...)
|
||||
|
||||
case exprs.NFT_COUNTER:
|
||||
defaultCounterName := "opensnitch"
|
||||
counterObj := &nftables.CounterObj{
|
||||
Table: &nftables.Table{Name: table, Family: nftables.TableFamilyIPv4},
|
||||
Name: defaultCounterName,
|
||||
Bytes: 0,
|
||||
Packets: 0,
|
||||
}
|
||||
for _, counterOption := range expression.Statement.Values {
|
||||
switch counterOption.Key {
|
||||
case exprs.NFT_COUNTER_NAME:
|
||||
defaultCounterName = counterOption.Value
|
||||
counterObj.Name = defaultCounterName
|
||||
case exprs.NFT_COUNTER_BYTES:
|
||||
// TODO: allow to set initial bytes/packets?
|
||||
counterObj.Bytes = 1
|
||||
case exprs.NFT_COUNTER_PACKETS:
|
||||
counterObj.Packets = 1
|
||||
}
|
||||
}
|
||||
n.conn.AddObj(counterObj)
|
||||
exprList = append(exprList, *exprs.NewExprCounter(defaultCounterName)...)
|
||||
}
|
||||
|
||||
return &exprList
|
||||
}
|
157
daemon/firewall/nftables/rule_helpers.go
Normal file
157
daemon/firewall/nftables/rule_helpers.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package nftables
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// rules examples: https://github.com/google/nftables/blob/master/nftables_test.go
|
||||
|
||||
func (n *Nft) buildICMPRule(table, family string, icmpOptions []*config.ExprValues) *[]expr.Any {
|
||||
tbl := getTable(table, family)
|
||||
if tbl == nil {
|
||||
return nil
|
||||
}
|
||||
offset := uint32(0)
|
||||
setType := nftables.TypeICMPType
|
||||
|
||||
exprICMP, _ := exprs.NewExprProtocol(exprs.NFT_PROTO_ICMP)
|
||||
ICMPrule := []expr.Any{}
|
||||
ICMPrule = append(ICMPrule, *exprICMP...)
|
||||
|
||||
ICMPtemp := []expr.Any{}
|
||||
setElements := []nftables.SetElement{}
|
||||
for _, icmp := range icmpOptions {
|
||||
switch icmp.Key {
|
||||
case exprs.NFT_ICMP_TYPE:
|
||||
exprCmp := &expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{exprs.GetICMPType(icmp.Value)},
|
||||
}
|
||||
ICMPtemp = append(ICMPtemp, []expr.Any{exprCmp}...)
|
||||
|
||||
// fill setElements. If there're more than 1 icmp type we'll use it later
|
||||
setElements = append(setElements,
|
||||
[]nftables.SetElement{
|
||||
{
|
||||
Key: []byte{exprs.GetICMPType(icmp.Value)},
|
||||
},
|
||||
}...)
|
||||
case exprs.NFT_ICMP_CODE:
|
||||
// TODO
|
||||
offset = 1
|
||||
}
|
||||
}
|
||||
|
||||
ICMPrule = append(ICMPrule, []expr.Any{
|
||||
&expr.Payload{
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseTransportHeader,
|
||||
Offset: offset, // 0 type, 1 code
|
||||
Len: 1,
|
||||
},
|
||||
}...)
|
||||
|
||||
if len(setElements) == 1 {
|
||||
ICMPrule = append(ICMPrule, ICMPtemp...)
|
||||
} else {
|
||||
set := &nftables.Set{
|
||||
Anonymous: true,
|
||||
Constant: true,
|
||||
Table: tbl,
|
||||
KeyType: setType,
|
||||
}
|
||||
if err := n.conn.AddSet(set, setElements); err != nil {
|
||||
log.Warning("%s AddSet() error: %s", logTag, err)
|
||||
return nil
|
||||
}
|
||||
sysSets = append(sysSets, []*nftables.Set{set}...)
|
||||
|
||||
ICMPrule = append(ICMPrule, []expr.Any{
|
||||
&expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetName: set.Name,
|
||||
SetID: set.ID,
|
||||
}}...)
|
||||
}
|
||||
|
||||
return &ICMPrule
|
||||
}
|
||||
|
||||
func (n *Nft) buildConntrackRule(ctOptions []*config.ExprValues) *[]expr.Any {
|
||||
exprList := []expr.Any{}
|
||||
|
||||
setMark := false
|
||||
for _, ctOption := range ctOptions {
|
||||
switch ctOption.Key {
|
||||
// we expect to have multiple "state" keys:
|
||||
// { "state": "established", "state": "related" }
|
||||
case exprs.NFT_CT_STATE:
|
||||
ctExprState, err := exprs.NewExprCtState(ctOptions)
|
||||
if err != nil {
|
||||
log.Warning("%s ct set state error: %s", logTag, err)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, *ctExprState...)
|
||||
exprList = append(exprList,
|
||||
&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}},
|
||||
)
|
||||
// we only need to iterate once here
|
||||
goto Exit
|
||||
case exprs.NFT_CT_SET_MARK:
|
||||
setMark = true
|
||||
case exprs.NFT_CT_MARK:
|
||||
ctExprMark, err := exprs.NewExprCtMark(setMark, ctOption.Value)
|
||||
if err != nil {
|
||||
log.Warning("%s ct mark error: %s", logTag, err)
|
||||
return nil
|
||||
}
|
||||
exprList = append(exprList, *ctExprMark...)
|
||||
goto Exit
|
||||
default:
|
||||
log.Warning("%s invalid conntrack option: %s", logTag, ctOption)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
Exit:
|
||||
return &exprList
|
||||
}
|
||||
|
||||
func (n *Nft) buildProtocolRule(table, family, ports string, cmpOp *expr.CmpOp) *[]expr.Any {
|
||||
tbl := getTable(table, family)
|
||||
if tbl == nil {
|
||||
return nil
|
||||
}
|
||||
exprList := []expr.Any{}
|
||||
if strings.Index(ports, ",") != -1 {
|
||||
set := &nftables.Set{
|
||||
Anonymous: true,
|
||||
Constant: true,
|
||||
Table: tbl,
|
||||
KeyType: nftables.TypeInetService,
|
||||
}
|
||||
setElements := exprs.NewExprPortSet(ports)
|
||||
if err := n.conn.AddSet(set, *setElements); err != nil {
|
||||
log.Warning("%s AddSet() error: %s", logTag, err)
|
||||
}
|
||||
exprList = append(exprList, &expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetName: set.Name,
|
||||
SetID: set.ID,
|
||||
})
|
||||
sysSets = append(sysSets, []*nftables.Set{set}...)
|
||||
} else if strings.Index(ports, "-") != -1 {
|
||||
exprList = append(exprList, *exprs.NewExprPortRange(ports)...)
|
||||
} else {
|
||||
exprList = append(exprList, *exprs.NewExprPort(ports, cmpOp)...)
|
||||
}
|
||||
|
||||
return &exprList
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
package nftables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/binaryutil"
|
||||
|
@ -9,65 +12,32 @@ import (
|
|||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (n *Nft) addGlobalTables() error {
|
||||
filter := n.conn.AddTable(filterTable)
|
||||
filter6 := n.conn.AddTable(filterTable6)
|
||||
|
||||
mangle := n.conn.AddTable(mangleTable)
|
||||
mangle6 := n.conn.AddTable(mangleTable6)
|
||||
n.mangleTables = []*nftables.Table{mangle, mangle6}
|
||||
n.filterTables = []*nftables.Table{filter, filter6}
|
||||
|
||||
// apply changes
|
||||
if err := n.conn.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: add more parameters, make it more generic
|
||||
func (n *Nft) addChain(name string, table *nftables.Table, prio nftables.ChainPriority, ctype nftables.ChainType, hook nftables.ChainHook) *nftables.Chain {
|
||||
// nft list chains
|
||||
return n.conn.AddChain(&nftables.Chain{
|
||||
Name: name,
|
||||
Table: table,
|
||||
Type: ctype,
|
||||
Hooknum: hook,
|
||||
Priority: prio,
|
||||
//Policy: nftables.ChainPolicyDrop
|
||||
})
|
||||
}
|
||||
|
||||
func (n *Nft) addGlobalChains() error {
|
||||
// nft list tables
|
||||
for _, table := range n.mangleTables {
|
||||
n.outputChains[table] = n.addChain(outputChain, table, nftables.ChainPriorityMangle, nftables.ChainTypeRoute, nftables.ChainHookOutput)
|
||||
}
|
||||
for _, table := range n.filterTables {
|
||||
n.inputChains[table] = n.addChain(inputChain, table, nftables.ChainPriorityFilter, nftables.ChainTypeFilter, nftables.ChainHookInput)
|
||||
}
|
||||
// apply changes
|
||||
if err := n.conn.Flush(); err != nil {
|
||||
log.Warning("Error adding nftables mangle tables: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// QueueDNSResponses redirects DNS responses to us, in order to keep a cache
|
||||
// of resolved domains.
|
||||
// This rule must be added in top of the system rules, otherwise it may get bypassed.
|
||||
// nft insert rule ip filter input udp sport 53 queue num 0 bypass
|
||||
func (n *Nft) QueueDNSResponses(enable bool, logError bool) (error, error) {
|
||||
if n.conn == nil {
|
||||
return nil, nil
|
||||
}
|
||||
for _, table := range n.filterTables {
|
||||
families := []string{exprs.NFT_FAMILY_INET}
|
||||
for _, fam := range families {
|
||||
table := getTable(exprs.NFT_CHAIN_FILTER, fam)
|
||||
chain := getChain(exprs.NFT_HOOK_INPUT, table)
|
||||
if table == nil {
|
||||
log.Error("QueueDNSResponses() Error getting table: %s-filter", fam)
|
||||
continue
|
||||
}
|
||||
if chain == nil {
|
||||
log.Error("QueueDNSResponses() Error getting chain: %s-%d", table.Name, table.Family)
|
||||
continue
|
||||
}
|
||||
|
||||
// nft list ruleset -a
|
||||
n.conn.InsertRule(&nftables.Rule{
|
||||
Position: 0,
|
||||
Table: table,
|
||||
Chain: n.inputChains[table],
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
|
||||
&expr.Cmp{
|
||||
|
@ -92,24 +62,62 @@ func (n *Nft) QueueDNSResponses(enable bool, logError bool) (error, error) {
|
|||
},
|
||||
},
|
||||
// rule key, to allow get it later by key
|
||||
UserData: []byte(fwKey),
|
||||
UserData: []byte(interceptionRuleKey),
|
||||
})
|
||||
}
|
||||
// apply changes
|
||||
if err := n.conn.Flush(); err != nil {
|
||||
return err, nil
|
||||
if !n.Commit() {
|
||||
return fmt.Errorf("Error adding DNS interception rules"), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// QueueConnections inserts the firewall rule which redirects connections to us.
|
||||
// They are queued until the user denies/accept them, or reaches a timeout.
|
||||
// Connections are queued until the user denies/accept them, or reaches a timeout.
|
||||
// This rule must be added at the end of all the other rules, that way we can add
|
||||
// rules above this one to exclude a service/app from being intercepted.
|
||||
// nft insert rule ip mangle OUTPUT ct state new queue num 0 bypass
|
||||
func (n *Nft) QueueConnections(enable bool, logError bool) (error, error) {
|
||||
if n.conn == nil {
|
||||
return nil, nil
|
||||
return nil, fmt.Errorf("nftables QueueConnections: netlink connection not active")
|
||||
}
|
||||
table := getTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET)
|
||||
if table == nil {
|
||||
return nil, fmt.Errorf("QueueConnections() Error getting table mangle-inet")
|
||||
}
|
||||
chain := getChain(exprs.NFT_HOOK_OUTPUT, table)
|
||||
if chain == nil {
|
||||
return nil, fmt.Errorf("QueueConnections() Error getting outputChain: output-%s", table.Name)
|
||||
}
|
||||
|
||||
n.conn.AddRule(&nftables.Rule{
|
||||
Position: 0,
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeySTATE},
|
||||
&expr.Bitwise{
|
||||
SourceRegister: 1,
|
||||
DestRegister: 1,
|
||||
Len: 4,
|
||||
Mask: binaryutil.NativeEndian.PutUint32(expr.CtStateBitNEW | expr.CtStateBitRELATED),
|
||||
Xor: binaryutil.NativeEndian.PutUint32(0),
|
||||
},
|
||||
&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}},
|
||||
&expr.Queue{
|
||||
Num: n.QueueNum,
|
||||
Flag: expr.QueueFlagBypass,
|
||||
},
|
||||
},
|
||||
// rule key, to allow get it later by key
|
||||
UserData: []byte(interceptionRuleKey),
|
||||
})
|
||||
// apply changes
|
||||
if !n.Commit() {
|
||||
return fmt.Errorf("Error adding interception rule "), nil
|
||||
}
|
||||
|
||||
if enable {
|
||||
// flush conntrack as soon as netfilter rule is set. This ensures that already-established
|
||||
// connections will go to netfilter queue.
|
||||
|
@ -118,84 +126,99 @@ func (n *Nft) QueueConnections(enable bool, logError bool) (error, error) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, table := range n.mangleTables {
|
||||
n.conn.InsertRule(&nftables.Rule{
|
||||
Position: 0,
|
||||
Table: table,
|
||||
Chain: n.outputChains[table],
|
||||
Exprs: []expr.Any{
|
||||
&expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeySTATE},
|
||||
&expr.Bitwise{
|
||||
SourceRegister: 1,
|
||||
DestRegister: 1,
|
||||
Len: 4,
|
||||
Mask: binaryutil.NativeEndian.PutUint32(expr.CtStateBitNEW | expr.CtStateBitRELATED),
|
||||
Xor: binaryutil.NativeEndian.PutUint32(0),
|
||||
},
|
||||
&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}},
|
||||
&expr.Queue{
|
||||
Num: n.QueueNum,
|
||||
Flag: expr.QueueFlagBypass,
|
||||
},
|
||||
},
|
||||
// rule key, to allow get it later by key
|
||||
UserData: []byte(fwKey),
|
||||
})
|
||||
}
|
||||
// apply changes
|
||||
if err := n.conn.Flush(); err != nil {
|
||||
return err, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *Nft) delInterceptionRules() {
|
||||
n.delRulesByKey(fwKey)
|
||||
func (n *Nft) insertRule(chain, table, family string, position uint64, exprs *[]expr.Any) error {
|
||||
tbl := getTable(table, family)
|
||||
if tbl == nil {
|
||||
return fmt.Errorf("%s addRule, Error getting table: %s, %s", logTag, table, family)
|
||||
}
|
||||
|
||||
chainKey := getChainKey(chain, tbl)
|
||||
chn := sysChains[chainKey]
|
||||
|
||||
rule := &nftables.Rule{
|
||||
Position: position,
|
||||
Table: tbl,
|
||||
Chain: chn,
|
||||
Exprs: *exprs,
|
||||
UserData: []byte(systemRuleKey),
|
||||
}
|
||||
n.conn.InsertRule(rule)
|
||||
if !n.Commit() {
|
||||
return fmt.Errorf("%s Error adding rule", logTag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nft) delRulesByKey(key string) {
|
||||
func (n *Nft) addRule(chain, table, family string, position uint64, exprs *[]expr.Any) error {
|
||||
tbl := getTable(table, family)
|
||||
if tbl == nil {
|
||||
return fmt.Errorf("%s addRule, Error getting table: %s, %s", logTag, table, family)
|
||||
}
|
||||
|
||||
chainKey := getChainKey(chain, tbl)
|
||||
chn := sysChains[chainKey]
|
||||
|
||||
rule := &nftables.Rule{
|
||||
Position: position,
|
||||
Table: tbl,
|
||||
Chain: chn,
|
||||
Exprs: *exprs,
|
||||
UserData: []byte(systemRuleKey),
|
||||
}
|
||||
n.conn.AddRule(rule)
|
||||
if !n.Commit() {
|
||||
return fmt.Errorf("%s Error adding rule", logTag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nft) delRulesByKey(key string) error {
|
||||
chains, err := n.conn.ListChains()
|
||||
if err != nil {
|
||||
log.Warning("nftables, error listing chains: %s", err)
|
||||
return
|
||||
return fmt.Errorf("error listing nftables chains (%s): %s", key, err)
|
||||
}
|
||||
commit := false
|
||||
for _, c := range chains {
|
||||
rules, err := n.conn.GetRule(c.Table, c)
|
||||
if err != nil {
|
||||
log.Warning("nftables, error listing rules (%s): %s", c.Table.Name, err)
|
||||
log.Warning("Error listing rules (%s): %s", key, err)
|
||||
continue
|
||||
}
|
||||
|
||||
commit = false
|
||||
delRules := 0
|
||||
for _, r := range rules {
|
||||
if string(r.UserData) != key {
|
||||
continue
|
||||
}
|
||||
// just passing the rule object doesn't work.
|
||||
// just passing the r 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 adding rule to be deleted (%s/%s): %s", c.Table.Name, c.Name, err)
|
||||
log.Warning("[nftables] error deleting rule (%s): %s", key, err)
|
||||
continue
|
||||
}
|
||||
commit = true
|
||||
delRules++
|
||||
}
|
||||
if commit {
|
||||
if err := n.conn.Flush(); err != nil {
|
||||
log.Warning("nftables, error deleting interception rules (%s/%s): %s", c.Table.Name, c.Name, err)
|
||||
if delRules > 0 {
|
||||
if !n.Commit() {
|
||||
log.Warning("%s error deleting rules: %s", logTag, err)
|
||||
}
|
||||
}
|
||||
if rules, err := n.conn.GetRule(c.Table, c); err == nil {
|
||||
if commit && len(rules) == 0 {
|
||||
n.conn.DelChain(c)
|
||||
n.conn.Flush()
|
||||
if len(rules) == 0 || len(rules) == delRules {
|
||||
if _, ok := sysChains[getChainKey(c.Name, c.Table)]; ok {
|
||||
n.delChain(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nft) delInterceptionRules() {
|
||||
n.delRulesByKey(interceptionRuleKey)
|
||||
}
|
||||
|
|
|
@ -1,40 +1,130 @@
|
|||
package nftables
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/iptables"
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
logTag = "nftables:"
|
||||
sysTables map[string]*nftables.Table
|
||||
sysChains map[string]*nftables.Chain
|
||||
sysSets []*nftables.Set
|
||||
)
|
||||
|
||||
func initMapsStore() {
|
||||
sysTables = make(map[string]*nftables.Table)
|
||||
sysChains = make(map[string]*nftables.Chain)
|
||||
}
|
||||
|
||||
// CreateSystemRule create the custom firewall chains and adds them to system.
|
||||
// nft insert rule ip opensnitch-filter opensnitch-input udp dport 1153
|
||||
func (n *Nft) CreateSystemRule(rule *config.FwRule, logErrors bool) {
|
||||
// TODO
|
||||
func (n *Nft) CreateSystemRule(chain *config.FwChain, logErrors bool) bool {
|
||||
if chain.IsInvalid() {
|
||||
log.Warning("%s CreateSystemRule(), Chain's field Name and Family cannot be empty", logTag)
|
||||
return false
|
||||
}
|
||||
|
||||
tableName := chain.Table
|
||||
n.AddTable(chain.Table, chain.Family)
|
||||
|
||||
// regular chains doesn't have a hook, nor a type
|
||||
if chain.Hook == "" && chain.Type == "" {
|
||||
n.addRegularChain(chain.Name, tableName, chain.Family)
|
||||
return n.Commit()
|
||||
}
|
||||
|
||||
chainPolicy := nftables.ChainPolicyAccept
|
||||
if iptables.Action(strings.ToLower(chain.Policy)) == exprs.VERDICT_DROP {
|
||||
chainPolicy = nftables.ChainPolicyDrop
|
||||
}
|
||||
|
||||
chainHook := getHook(chain.Hook)
|
||||
chainPrio, chainType := getChainPriority(chain.Family, chain.Type, chain.Hook)
|
||||
if chainPrio == nil {
|
||||
log.Warning("%s Invalid system firewall combination: %s, %s", logTag, chain.Type, chain.Hook)
|
||||
return false
|
||||
}
|
||||
|
||||
if ret := n.AddChain(chain.Name, chain.Table, chain.Family, *chainPrio,
|
||||
chainType, *chainHook, chainPolicy); ret == nil {
|
||||
log.Warning("%s error adding chain: %s, table: %s", logTag, chain.Name, chain.Table)
|
||||
return false
|
||||
}
|
||||
|
||||
return n.Commit()
|
||||
}
|
||||
|
||||
// AddSystemRules creates the system firewall from configuration.
|
||||
func (n *Nft) AddSystemRules(reload bool) {
|
||||
n.SysConfig.RLock()
|
||||
defer n.SysConfig.RUnlock()
|
||||
|
||||
if n.SysConfig.Enabled == false {
|
||||
log.Important("[nftables] AddSystemRules() fw disabled")
|
||||
return
|
||||
}
|
||||
|
||||
for _, fwCfg := range n.SysConfig.SystemRules {
|
||||
for _, chain := range fwCfg.Chains {
|
||||
if !n.CreateSystemRule(chain, true) {
|
||||
log.Info("createSystem failed: %s %s", chain.Name, chain.Table)
|
||||
continue
|
||||
}
|
||||
for i := len(chain.Rules) - 1; i >= 0; i-- {
|
||||
if chain.Rules[i].UUID == "" {
|
||||
uuid := uuid.New()
|
||||
chain.Rules[i].UUID = uuid.String()
|
||||
}
|
||||
if chain.Rules[i].Enabled {
|
||||
n.AddSystemRule(chain.Rules[i], chain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSystemRules deletes the system rules.
|
||||
// If force is false and the rule has not been previously added,
|
||||
// it won't try to delete the rules. Otherwise it'll try to delete them.
|
||||
// it won't try to delete the tables and chains. Otherwise it'll try to delete them.
|
||||
func (n *Nft) DeleteSystemRules(force, logErrors bool) {
|
||||
// TODO
|
||||
}
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
// AddSystemRule inserts a new rule.
|
||||
func (n *Nft) AddSystemRule(rule *config.FwRule, enable bool) (error, error) {
|
||||
// TODO
|
||||
return nil, nil
|
||||
}
|
||||
if err := n.delRulesByKey(systemRuleKey); err != nil {
|
||||
log.Warning("error deleting interception rules: %s", err)
|
||||
}
|
||||
|
||||
// AddSystemRules creates the system firewall from configuration
|
||||
func (n *Nft) AddSystemRules() {
|
||||
n.DeleteSystemRules(true, false)
|
||||
|
||||
for _, r := range n.SysConfig.SystemRules {
|
||||
n.CreateSystemRule(r.Rule, true)
|
||||
n.AddSystemRule(r.Rule, true)
|
||||
if force {
|
||||
n.delSystemTables()
|
||||
}
|
||||
}
|
||||
|
||||
// preloadConfCallback gets called before the fw configuration is reloaded
|
||||
func (n *Nft) preloadConfCallback() {
|
||||
n.DeleteSystemRules(true, log.GetLogLevel() == log.DEBUG)
|
||||
// AddSystemRule inserts a new rule.
|
||||
func (n *Nft) AddSystemRule(rule *config.FwRule, chain *config.FwChain) (err4, err6 error) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
exprList := []expr.Any{}
|
||||
|
||||
for _, expression := range rule.Expressions {
|
||||
if exprsOfRule := n.parseExpression(chain.Table, chain.Name, chain.Family, expression); exprsOfRule != nil {
|
||||
exprList = append(exprList, *exprsOfRule...)
|
||||
}
|
||||
}
|
||||
if len(exprList) > 0 {
|
||||
exprVerdict := exprs.NewExprVerdict(rule.Target, rule.TargetParameters)
|
||||
exprList = append(exprList, *exprVerdict...)
|
||||
if err := n.insertRule(chain.Name, chain.Table, chain.Family, rule.Position, &exprList); err != nil {
|
||||
log.Warning("error adding rule: %v", rule)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
|
86
daemon/firewall/nftables/tables.go
Normal file
86
daemon/firewall/nftables/tables.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package nftables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/google/nftables"
|
||||
)
|
||||
|
||||
// AddTable adds a new table to nftables.
|
||||
func (n *Nft) AddTable(name, family string) *nftables.Table {
|
||||
famCode := getFamilyCode(family)
|
||||
tbl := &nftables.Table{
|
||||
Family: famCode,
|
||||
Name: name,
|
||||
}
|
||||
n.conn.AddTable(tbl)
|
||||
|
||||
if !n.Commit() {
|
||||
log.Error("%s error adding system firewall table: %s, family: %s (%d)", logTag, name, family, famCode)
|
||||
return nil
|
||||
}
|
||||
key := getTableKey(name, family)
|
||||
sysTables[key] = tbl
|
||||
return tbl
|
||||
}
|
||||
|
||||
func getTable(name, family string) *nftables.Table {
|
||||
return sysTables[getTableKey(name, family)]
|
||||
}
|
||||
|
||||
func getTableKey(name string, family interface{}) string {
|
||||
return fmt.Sprint(name, "-", family)
|
||||
}
|
||||
|
||||
func (n *Nft) addInterceptionTables() error {
|
||||
n.AddTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET)
|
||||
n.AddTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Contrary to iptables, in nftables there're no predefined rules.
|
||||
// Convention is though to use the iptables names by default.
|
||||
// We need at least: mangle and filter tables, inet family (IPv4 and IPv6).
|
||||
func (n *Nft) addSystemTables() {
|
||||
n.AddTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET)
|
||||
n.AddTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
|
||||
}
|
||||
|
||||
// return the number of rules that we didn't add.
|
||||
func (n *Nft) nonSystemRules(tbl *nftables.Table) int {
|
||||
chains, err := n.conn.ListChains()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
t := 0
|
||||
for _, c := range chains {
|
||||
if tbl.Name != c.Table.Name && tbl.Family != c.Table.Family {
|
||||
continue
|
||||
}
|
||||
rules, err := n.conn.GetRule(c.Table, c)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
t += len(rules)
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// FIXME: if the user configured chains policies to drop and disables the firewall,
|
||||
// the policy is not restored.
|
||||
func (n *Nft) delSystemTables() {
|
||||
for k, tbl := range sysTables {
|
||||
if n.nonSystemRules(tbl) != 0 {
|
||||
continue
|
||||
}
|
||||
n.conn.DelTable(tbl)
|
||||
if !n.Commit() {
|
||||
log.Warning("error deleting system tables")
|
||||
continue
|
||||
}
|
||||
delete(sysTables, k)
|
||||
}
|
||||
}
|
173
daemon/firewall/nftables/utils.go
Normal file
173
daemon/firewall/nftables/utils.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package nftables
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/google/nftables"
|
||||
)
|
||||
|
||||
func getFamilyCode(family string) nftables.TableFamily {
|
||||
famCode := nftables.TableFamilyINet
|
||||
switch family {
|
||||
// [filter]: prerouting forward input output postrouting
|
||||
// [nat]: prerouting, input output postrouting
|
||||
// [route]: output
|
||||
case exprs.NFT_FAMILY_IP6:
|
||||
famCode = nftables.TableFamilyIPv6
|
||||
case exprs.NFT_FAMILY_IP:
|
||||
famCode = nftables.TableFamilyIPv4
|
||||
case exprs.NFT_FAMILY_BRIDGE:
|
||||
// [filter]: prerouting forward input output postrouting
|
||||
famCode = nftables.TableFamilyBridge
|
||||
case exprs.NFT_FAMILY_ARP:
|
||||
// [filter]: input output
|
||||
famCode = nftables.TableFamilyARP
|
||||
case exprs.NFT_FAMILY_NETDEV:
|
||||
// [filter]: egress, ingress
|
||||
famCode = nftables.TableFamilyNetdev
|
||||
}
|
||||
|
||||
return famCode
|
||||
}
|
||||
|
||||
func getHook(chain string) *nftables.ChainHook {
|
||||
hook := nftables.ChainHookOutput
|
||||
|
||||
// https://github.com/google/nftables/blob/master/chain.go#L33
|
||||
switch strings.ToLower(chain) {
|
||||
case exprs.NFT_HOOK_INPUT:
|
||||
hook = nftables.ChainHookInput
|
||||
case exprs.NFT_HOOK_PREROUTING:
|
||||
hook = nftables.ChainHookPrerouting
|
||||
case exprs.NFT_HOOK_POSTROUTING:
|
||||
hook = nftables.ChainHookPostrouting
|
||||
case exprs.NFT_HOOK_FORWARD:
|
||||
hook = nftables.ChainHookForward
|
||||
case exprs.NFT_HOOK_INGRESS:
|
||||
hook = nftables.ChainHookIngress
|
||||
}
|
||||
|
||||
return &hook
|
||||
}
|
||||
|
||||
// getChainPriority gets the corresponding priority for the given chain, based
|
||||
// on the following configuration matrix:
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
|
||||
// https://github.com/google/nftables/blob/master/chain.go#L48
|
||||
// man nft (table 6.)
|
||||
func getChainPriority(family, cType, hook string) (*nftables.ChainPriority, nftables.ChainType) {
|
||||
// types: route, nat, filter
|
||||
chainType := nftables.ChainTypeFilter
|
||||
// priorities: raw, conntrack, mangle, natdest, filter, security
|
||||
chainPrio := nftables.ChainPriorityFilter
|
||||
|
||||
family = strings.ToLower(family)
|
||||
cType = strings.ToLower(cType)
|
||||
hook = strings.ToLower(hook)
|
||||
|
||||
// constraints
|
||||
// https://www.netfilter.org/projects/nftables/manpage.html#lbAQ
|
||||
if (cType == exprs.NFT_CHAIN_NATDEST || cType == exprs.NFT_CHAIN_NATSOURCE) && hook == exprs.NFT_HOOK_FORWARD {
|
||||
log.Warning("[nftables] invalid nat combination of tables and hooks. chain: %s, hook: %s", cType, hook)
|
||||
return nil, chainType
|
||||
}
|
||||
if family == exprs.NFT_FAMILY_NETDEV && (cType != exprs.NFT_CHAIN_FILTER || (hook != exprs.NFT_HOOK_EGRESS || hook != exprs.NFT_HOOK_INGRESS)) {
|
||||
log.Warning("[nftables] invalid netdev combination of tables and hooks. chain: %s, hook: %s", cType, hook)
|
||||
return nil, chainType
|
||||
}
|
||||
if family == exprs.NFT_FAMILY_ARP && (cType != exprs.NFT_CHAIN_FILTER || (hook != exprs.NFT_HOOK_OUTPUT || hook != exprs.NFT_HOOK_INPUT)) {
|
||||
log.Warning("[nftables] invalid arp combination of tables and hooks. chain: %s, hook: %s", cType, hook)
|
||||
return nil, chainType
|
||||
}
|
||||
if family == exprs.NFT_FAMILY_BRIDGE && (cType != exprs.NFT_CHAIN_FILTER || (hook == exprs.NFT_HOOK_EGRESS || hook == exprs.NFT_HOOK_INGRESS)) {
|
||||
log.Warning("[nftables] invalid bridge combination of tables and hooks. chain: %s, hook: %s", cType, hook)
|
||||
return nil, chainType
|
||||
}
|
||||
|
||||
// Standard priority names, family and hook compatibility matrix
|
||||
// https://www.netfilter.org/projects/nftables/manpage.html#lbAQ
|
||||
|
||||
switch cType {
|
||||
case exprs.NFT_CHAIN_FILTER:
|
||||
if family == exprs.NFT_FAMILY_BRIDGE {
|
||||
// bridge all filter -200 NF_BR_PRI_FILTER_BRIDGED
|
||||
chainPrio = nftables.ChainPriorityConntrack
|
||||
switch hook {
|
||||
case exprs.NFT_HOOK_PREROUTING: // -300
|
||||
chainPrio = nftables.ChainPriorityRaw
|
||||
case exprs.NFT_HOOK_OUTPUT: // -100
|
||||
chainPrio = nftables.ChainPriorityNATSource
|
||||
case exprs.NFT_HOOK_POSTROUTING: // 300
|
||||
chainPrio = nftables.ChainPriorityConntrackHelper
|
||||
}
|
||||
}
|
||||
case exprs.NFT_CHAIN_MANGLE:
|
||||
// hooks: all
|
||||
// XXX: check hook input?
|
||||
chainPrio = nftables.ChainPriorityMangle
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_types
|
||||
// (...) equivalent semantics to the mangle table but only for the output hook (for other hooks use type filter instead).
|
||||
|
||||
// Despite of what is said on the wiki, mangle chains must be of filter type,
|
||||
// otherwise on some kernels (4.19.x) table MANGLE hook OUTPUT chain is not created
|
||||
chainType = nftables.ChainTypeFilter
|
||||
|
||||
case exprs.NFT_CHAIN_RAW:
|
||||
// hook: all
|
||||
chainPrio = nftables.ChainPriorityRaw
|
||||
|
||||
case exprs.NFT_CHAIN_CONNTRACK:
|
||||
chainPrio, chainType = getConntrackPriority(hook)
|
||||
|
||||
case exprs.NFT_CHAIN_NATDEST:
|
||||
// hook: prerouting
|
||||
chainPrio = nftables.ChainPriorityNATDest
|
||||
switch hook {
|
||||
case exprs.NFT_HOOK_OUTPUT:
|
||||
chainPrio = nftables.ChainPriorityNATSource
|
||||
}
|
||||
chainType = nftables.ChainTypeNAT
|
||||
|
||||
case exprs.NFT_CHAIN_NATSOURCE:
|
||||
// hook: postrouting
|
||||
chainPrio = nftables.ChainPriorityNATSource
|
||||
chainType = nftables.ChainTypeNAT
|
||||
|
||||
case exprs.NFT_CHAIN_SECURITY:
|
||||
// hook: all
|
||||
chainPrio = nftables.ChainPrioritySecurity
|
||||
|
||||
case exprs.NFT_CHAIN_SELINUX:
|
||||
// hook: all
|
||||
if hook != exprs.NFT_HOOK_POSTROUTING {
|
||||
chainPrio = nftables.ChainPrioritySELinuxLast
|
||||
} else {
|
||||
chainPrio = nftables.ChainPrioritySELinuxFirst
|
||||
}
|
||||
}
|
||||
|
||||
return &chainPrio, chainType
|
||||
}
|
||||
|
||||
// https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
|
||||
func getConntrackPriority(hook string) (nftables.ChainPriority, nftables.ChainType) {
|
||||
chainType := nftables.ChainTypeFilter
|
||||
chainPrio := nftables.ChainPriorityConntrack
|
||||
switch hook {
|
||||
case exprs.NFT_HOOK_PREROUTING:
|
||||
chainPrio = nftables.ChainPriorityConntrack
|
||||
// ChainTypeNAT not allowed here
|
||||
case exprs.NFT_HOOK_OUTPUT:
|
||||
chainPrio = nftables.ChainPriorityNATSource // 100 - ChainPriorityConntrack
|
||||
case exprs.NFT_HOOK_POSTROUTING:
|
||||
chainPrio = nftables.ChainPriorityConntrackHelper
|
||||
chainType = nftables.ChainTypeNAT
|
||||
case exprs.NFT_HOOK_INPUT:
|
||||
// can also be hook == NFT_HOOK_POSTROUTING
|
||||
chainPrio = nftables.ChainPriorityConntrackConfirm
|
||||
}
|
||||
|
||||
return chainPrio, chainType
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package firewall
|
||||
|
||||
import (
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/config"
|
||||
"fmt"
|
||||
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/iptables"
|
||||
"github.com/evilsocket/opensnitch/daemon/firewall/nftables"
|
||||
"github.com/evilsocket/opensnitch/daemon/log"
|
||||
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
|
||||
)
|
||||
|
||||
// Firewall is the interface that all firewalls (iptables, nftables) must implement.
|
||||
|
@ -15,18 +17,62 @@ type Firewall interface {
|
|||
IsRunning() bool
|
||||
SetQueueNum(num *int)
|
||||
|
||||
InsertRules()
|
||||
SaveConfiguration(rawConfig string) error
|
||||
|
||||
EnableInterception()
|
||||
DisableInterception(bool)
|
||||
QueueDNSResponses(bool, bool) (error, error)
|
||||
QueueConnections(bool, bool) (error, error)
|
||||
CleanRules(bool)
|
||||
|
||||
AddSystemRules()
|
||||
AddSystemRules(bool)
|
||||
DeleteSystemRules(bool, bool)
|
||||
AddSystemRule(*config.FwRule, bool) (error, error)
|
||||
CreateSystemRule(*config.FwRule, bool)
|
||||
|
||||
Serialize() (*protocol.SysFirewall, error)
|
||||
Deserialize(sysfw *protocol.SysFirewall) ([]byte, error)
|
||||
}
|
||||
|
||||
var fw Firewall
|
||||
var (
|
||||
fw Firewall
|
||||
queueNum = 0
|
||||
)
|
||||
|
||||
// Init initializes the firewall and loads firewall rules.
|
||||
// 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) {
|
||||
var err error
|
||||
|
||||
if fwType == iptables.Name {
|
||||
fw, err = iptables.Fw()
|
||||
if err != nil {
|
||||
log.Warning("iptables not available: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if fwType == nftables.Name || err != nil {
|
||||
fw, err = nftables.Fw()
|
||||
if err != nil {
|
||||
log.Warning("nftables not available: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error("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)
|
||||
queueNum = *qNum
|
||||
|
||||
log.Info("Using %s firewall", fw.Name())
|
||||
}
|
||||
|
||||
// IsRunning returns if the firewall is running or not.
|
||||
func IsRunning() bool {
|
||||
|
@ -41,6 +87,36 @@ func CleanRules(logErrors bool) {
|
|||
fw.CleanRules(logErrors)
|
||||
}
|
||||
|
||||
// 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(false, true)
|
||||
fw.AddSystemRules(true)
|
||||
}
|
||||
|
||||
// EnableInterception removes the rules to intercept outbound connections.
|
||||
func EnableInterception() error {
|
||||
if fw == nil {
|
||||
return fmt.Errorf("firewall not initialized when trying to enable interception, report please")
|
||||
}
|
||||
fw.EnableInterception()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableInterception removes the rules to intercept outbound connections.
|
||||
func DisableInterception() error {
|
||||
if fw == nil {
|
||||
return fmt.Errorf("firewall not initialized when trying to disable interception, report please")
|
||||
}
|
||||
fw.DisableInterception(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop deletes the firewall rules, allowing network traffic.
|
||||
func Stop() {
|
||||
if fw == nil {
|
||||
|
@ -49,37 +125,17 @@ func Stop() {
|
|||
fw.Stop()
|
||||
}
|
||||
|
||||
// Init initializes the firewall and loads firewall rules.
|
||||
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())
|
||||
// SaveConfiguration saves configuration string to disk
|
||||
func SaveConfiguration(rawConfig []byte) error {
|
||||
return fw.SaveConfiguration(string(rawConfig))
|
||||
}
|
||||
|
||||
// Serialize transforms firewall json configuration to protobuf
|
||||
func Serialize() (*protocol.SysFirewall, error) {
|
||||
return fw.Serialize()
|
||||
}
|
||||
|
||||
// Deserialize transforms firewall json configuration to protobuf
|
||||
func Deserialize(sysfw *protocol.SysFirewall) ([]byte, error) {
|
||||
return fw.Deserialize(sysfw)
|
||||
}
|
||||
|
|
|
@ -5,15 +5,14 @@ go 1.14
|
|||
require (
|
||||
github.com/evilsocket/ftrace v1.2.0
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/golang/protobuf v1.5.0
|
||||
github.com/google/gopacket v1.1.14
|
||||
github.com/google/nftables v0.0.0-20220210072902-edf9fe8cd04f
|
||||
github.com/google/nftables v0.0.0-20220329160011-5a9391c12fe3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/iovisor/gobpf v0.2.0
|
||||
github.com/vishvananda/netlink v0.0.0-20210811191823-e1a867c6b452
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
||||
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1
|
||||
golang.org/x/text v0.3.0 // indirect
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d
|
||||
google.golang.org/grpc v1.32.0
|
||||
google.golang.org/protobuf v1.26.0 // indirect
|
||||
)
|
||||
|
|
|
@ -1,14 +1,238 @@
|
|||
{
|
||||
"SystemRules": [
|
||||
"Enabled": true,
|
||||
"Version": 1,
|
||||
"SystemRules": [
|
||||
{
|
||||
"Rule": {
|
||||
"Table": "mangle",
|
||||
"Chain": "OUTPUT",
|
||||
"Enabled": false,
|
||||
"Position": "0",
|
||||
"Description": "Allow icmp",
|
||||
"Parameters": "-p icmp",
|
||||
"Expressions": [],
|
||||
"Target": "ACCEPT",
|
||||
"TargetParameters": ""
|
||||
},
|
||||
"Chains": []
|
||||
},
|
||||
{
|
||||
"Chains": [
|
||||
{
|
||||
"Rule": {
|
||||
"Description": "Allow icmp",
|
||||
"Table": "mangle",
|
||||
"Chain": "OUTPUT",
|
||||
"Parameters": "-p icmp",
|
||||
"Target": "ACCEPT",
|
||||
"TargetParameters": ""
|
||||
"Name": "forward",
|
||||
"Table": "filter",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "filter",
|
||||
"Hook": "forward",
|
||||
"Policy": "accept",
|
||||
"Rules": []
|
||||
},
|
||||
{
|
||||
"Name": "output",
|
||||
"Table": "filter",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "filter",
|
||||
"Hook": "output",
|
||||
"Policy": "accept",
|
||||
"Rules": []
|
||||
},
|
||||
{
|
||||
"Name": "input",
|
||||
"Table": "filter",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "filter",
|
||||
"Hook": "input",
|
||||
"Policy": "accept",
|
||||
"Rules": [
|
||||
{
|
||||
"Enabled": false,
|
||||
"Position": "0",
|
||||
"Description": "Allow SSH server connections when input policy is DROP",
|
||||
"Parameters": "",
|
||||
"Expressions": [
|
||||
{
|
||||
"Statement": {
|
||||
"Op": "",
|
||||
"Name": "tcp",
|
||||
"Values": [
|
||||
{
|
||||
"Key": "dport",
|
||||
"Value": "22"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Target": "accept",
|
||||
"TargetParameters": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "filter-prerouting",
|
||||
"Table": "nat",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "filter",
|
||||
"Hook": "prerouting",
|
||||
"Policy": "accept",
|
||||
"Rules": []
|
||||
},
|
||||
{
|
||||
"Name": "prerouting",
|
||||
"Table": "mangle",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "mangle",
|
||||
"Hook": "prerouting",
|
||||
"Policy": "accept",
|
||||
"Rules": []
|
||||
},
|
||||
{
|
||||
"Name": "postrouting",
|
||||
"Table": "mangle",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "mangle",
|
||||
"Hook": "postrouting",
|
||||
"Policy": "accept",
|
||||
"Rules": []
|
||||
},
|
||||
{
|
||||
"Name": "prerouting",
|
||||
"Table": "nat",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "natdest",
|
||||
"Hook": "prerouting",
|
||||
"Policy": "accept",
|
||||
"Rules": []
|
||||
},
|
||||
{
|
||||
"Name": "postrouting",
|
||||
"Table": "nat",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "natsource",
|
||||
"Hook": "postrouting",
|
||||
"Policy": "accept",
|
||||
"Rules": []
|
||||
},
|
||||
{
|
||||
"Name": "input",
|
||||
"Table": "nat",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "natsource",
|
||||
"Hook": "input",
|
||||
"Policy": "accept",
|
||||
"Rules": []
|
||||
},
|
||||
{
|
||||
"Name": "output",
|
||||
"Table": "nat",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "natdest",
|
||||
"Hook": "output",
|
||||
"Policy": "accept",
|
||||
"Rules": []
|
||||
},
|
||||
{
|
||||
"Name": "output",
|
||||
"Table": "mangle",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "mangle",
|
||||
"Hook": "output",
|
||||
"Policy": "accept",
|
||||
"Rules": [
|
||||
{
|
||||
"Enabled": true,
|
||||
"Position": "0",
|
||||
"Description": "Allow ICMP",
|
||||
"Expressions": [
|
||||
{
|
||||
"Statement": {
|
||||
"Op": "",
|
||||
"Name": "icmp",
|
||||
"Values": [
|
||||
{
|
||||
"Key": "type",
|
||||
"Value": "echo-request"
|
||||
},
|
||||
{
|
||||
"Key": "type",
|
||||
"Value": "echo-reply"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Target": "accept",
|
||||
"TargetParameters": ""
|
||||
},
|
||||
{
|
||||
"Enabled": false,
|
||||
"Position": "0",
|
||||
"Description": "Exclude WireGuard VPN from being intercepted",
|
||||
"Parameters": "",
|
||||
"Expressions": [
|
||||
{
|
||||
"Statement": {
|
||||
"Op": "",
|
||||
"Name": "tcp",
|
||||
"Values": [
|
||||
{
|
||||
"Key": "dport",
|
||||
"Value": "51820"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Target": "accept",
|
||||
"TargetParameters": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "forward",
|
||||
"Table": "mangle",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "mangle",
|
||||
"Hook": "forward",
|
||||
"Policy": "accept",
|
||||
"Rules": [
|
||||
{
|
||||
"UUID": "7d7394e1-100d-4b87-a90a-cd68c46edb0b",
|
||||
"Enabled": false,
|
||||
"Position": "0",
|
||||
"Description": "Intercept forwarded connections (docker, etc)",
|
||||
"Expressions": [
|
||||
{
|
||||
"Statement": {
|
||||
"Op": "",
|
||||
"Name": "ct",
|
||||
"Values": [
|
||||
{
|
||||
"Key": "state",
|
||||
"Value": "new"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Target": "queue",
|
||||
"TargetParameters": "num 0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -42,6 +42,10 @@ func (c *Client) getClientConfig() *protocol.ClientConfig {
|
|||
ruleList[idx] = r.Serialize()
|
||||
idx++
|
||||
}
|
||||
sysfw, err := firewall.Serialize()
|
||||
if err != nil {
|
||||
log.Warning("firewall.Serialize() error: %s", err)
|
||||
}
|
||||
return &protocol.ClientConfig{
|
||||
Id: uint64(ts.UnixNano()),
|
||||
Name: nodeName,
|
||||
|
@ -50,6 +54,7 @@ func (c *Client) getClientConfig() *protocol.ClientConfig {
|
|||
Config: strings.Replace(string(raw), "\n", "", -1),
|
||||
LogLevel: uint32(log.MinLevel),
|
||||
Rules: ruleList,
|
||||
SystemFirewall: sysfw,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,14 +199,37 @@ func (c *Client) handleNotification(stream protocol.UI_NotificationsClient, noti
|
|||
case notification.Type == protocol.Action_CHANGE_CONFIG:
|
||||
c.handleActionChangeConfig(stream, notification)
|
||||
|
||||
case notification.Type == protocol.Action_LOAD_FIREWALL:
|
||||
log.Info("[notification] starting firewall")
|
||||
firewall.Init(c.GetFirewallType(), nil)
|
||||
case notification.Type == protocol.Action_ENABLE_INTERCEPTION:
|
||||
log.Info("[notification] starting interception")
|
||||
if err := firewall.EnableInterception(); err != nil {
|
||||
log.Warning("firewall.EnableInterception() error: %s", err)
|
||||
c.sendNotificationReply(stream, notification.Id, "", err)
|
||||
return
|
||||
}
|
||||
c.sendNotificationReply(stream, notification.Id, "", nil)
|
||||
|
||||
case notification.Type == protocol.Action_UNLOAD_FIREWALL:
|
||||
log.Info("[notification] stopping firewall")
|
||||
firewall.Stop()
|
||||
case notification.Type == protocol.Action_DISABLE_INTERCEPTION:
|
||||
log.Info("[notification] stopping interception")
|
||||
if err := firewall.DisableInterception(); err != nil {
|
||||
log.Warning("firewall.DisableInterception() error: %s", err)
|
||||
c.sendNotificationReply(stream, notification.Id, "", err)
|
||||
return
|
||||
}
|
||||
c.sendNotificationReply(stream, notification.Id, "", nil)
|
||||
|
||||
case notification.Type == protocol.Action_RELOAD_FW_RULES:
|
||||
log.Info("[notification] reloading firewall")
|
||||
|
||||
sysfw, err := firewall.Deserialize(notification.SysFirewall)
|
||||
if err != nil {
|
||||
log.Warning("firewall.Deserialize() error: %s", err)
|
||||
c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("Error reloading firewall, invalid rules"))
|
||||
return
|
||||
}
|
||||
if err := firewall.SaveConfiguration(sysfw); err != nil {
|
||||
c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("Error saving system firewall rules: %s", err))
|
||||
return
|
||||
}
|
||||
c.sendNotificationReply(stream, notification.Id, "", nil)
|
||||
|
||||
// ENABLE_RULE just replaces the rule on disk
|
||||
|
|
|
@ -80,17 +80,73 @@ message Rule {
|
|||
|
||||
enum Action {
|
||||
NONE = 0;
|
||||
LOAD_FIREWALL = 1;
|
||||
UNLOAD_FIREWALL = 2;
|
||||
CHANGE_CONFIG = 3;
|
||||
ENABLE_RULE = 4;
|
||||
DISABLE_RULE = 5;
|
||||
DELETE_RULE = 6;
|
||||
CHANGE_RULE = 7;
|
||||
LOG_LEVEL = 8;
|
||||
STOP = 9;
|
||||
MONITOR_PROCESS = 10;
|
||||
STOP_MONITOR_PROCESS = 11;
|
||||
ENABLE_INTERCEPTION = 1;
|
||||
DISABLE_INTERCEPTION = 2;
|
||||
ENABLE_FIREWALL = 3;
|
||||
DISABLE_FIREWALL = 4;
|
||||
RELOAD_FW_RULES = 5;
|
||||
CHANGE_CONFIG = 6;
|
||||
ENABLE_RULE = 7;
|
||||
DISABLE_RULE = 8;
|
||||
DELETE_RULE = 9;
|
||||
CHANGE_RULE = 10;
|
||||
LOG_LEVEL = 11;
|
||||
STOP = 12;
|
||||
MONITOR_PROCESS = 13;
|
||||
STOP_MONITOR_PROCESS = 14;
|
||||
}
|
||||
|
||||
message StatementValues {
|
||||
string Key = 1;
|
||||
string Value = 2;
|
||||
}
|
||||
|
||||
message Statement {
|
||||
string Op = 1;
|
||||
string Name = 2;
|
||||
repeated StatementValues Values = 3;
|
||||
}
|
||||
|
||||
message Expressions {
|
||||
Statement Statement = 1;
|
||||
}
|
||||
|
||||
message FwRule {
|
||||
// DEPRECATED: for backward compatibility with iptables
|
||||
string Table = 1;
|
||||
string Chain = 2;
|
||||
|
||||
string UUID = 3;
|
||||
bool Enabled = 4;
|
||||
uint64 Position = 5;
|
||||
string Description = 6;
|
||||
string Parameters = 7;
|
||||
repeated Expressions Expressions = 8;
|
||||
string Target = 9;
|
||||
string TargetParameters = 10;
|
||||
}
|
||||
|
||||
message FwChain {
|
||||
string Name = 1;
|
||||
string Table = 2;
|
||||
string Family = 3;
|
||||
string Priority = 4;
|
||||
string Type = 5;
|
||||
string Hook = 6;
|
||||
string Policy = 7;
|
||||
repeated FwRule Rules = 8;
|
||||
}
|
||||
|
||||
message FwChains {
|
||||
// DEPRECATED: backward compatibility with iptables
|
||||
FwRule Rule = 1;
|
||||
repeated FwChain Chains = 2;
|
||||
}
|
||||
|
||||
message SysFirewall {
|
||||
bool Enabled = 1;
|
||||
uint32 Version = 2;
|
||||
repeated FwChains SystemRules = 3;
|
||||
}
|
||||
|
||||
// client configuration sent on Subscribe()
|
||||
|
@ -103,6 +159,7 @@ message ClientConfig {
|
|||
string config = 5;
|
||||
uint32 logLevel = 6;
|
||||
repeated Rule rules = 7;
|
||||
SysFirewall systemFirewall = 8;
|
||||
}
|
||||
|
||||
// notification sent to the clients (daemons)
|
||||
|
@ -114,6 +171,7 @@ message Notification {
|
|||
Action type = 4;
|
||||
string data = 5;
|
||||
repeated Rule rules = 6;
|
||||
SysFirewall sysFirewall = 7;
|
||||
}
|
||||
|
||||
// notification reply sent to the server (GUI)
|
||||
|
|
|
@ -6,6 +6,7 @@ class Config:
|
|||
|
||||
HELP_URL = "https://github.com/evilsocket/opensnitch/wiki/"
|
||||
HELP_RULES_URL = "https://github.com/evilsocket/opensnitch/wiki/Rules"
|
||||
HELP_SYS_RULES_URL = "https://github.com/evilsocket/opensnitch/wiki/System-rules"
|
||||
HELP_CONFIG_URL = "https://github.com/evilsocket/opensnitch/wiki/Configurations"
|
||||
|
||||
RULE_TYPE_LIST = "list"
|
||||
|
@ -26,6 +27,19 @@ class Config:
|
|||
ACTION_ALLOW = "allow"
|
||||
ACTION_DENY = "deny"
|
||||
ACTION_REJECT = "reject"
|
||||
ACTION_ACCEPT = "accept"
|
||||
ACTION_DROP = "drop"
|
||||
ACTION_JUMP = "jump"
|
||||
ACTION_REDIRECT = "redirect"
|
||||
ACTION_RETURN = "return"
|
||||
ACTION_TPROXY = "tproxy"
|
||||
ACTION_SNAT = "snat"
|
||||
ACTION_DNAT = "dnat"
|
||||
ACTION_MASQUERADE = "masquerade"
|
||||
ACTION_QUEUE = "queue"
|
||||
ACTION_LOG = "log"
|
||||
ACTION_STOP = "stop"
|
||||
|
||||
DURATION_UNTIL_RESTART = "until restart"
|
||||
DURATION_ALWAYS = "always"
|
||||
DURATION_ONCE = "once"
|
||||
|
@ -77,6 +91,7 @@ class Config:
|
|||
STATS_GENERAL_FILTER_TEXT = "statsDialog/"
|
||||
STATS_GENERAL_FILTER_ACTION = "statsDialog/"
|
||||
STATS_RULES_COL_STATE = "statsDialog/rules_columns_state"
|
||||
STATS_FW_COL_STATE = "statsDialog/firewall_columns_state"
|
||||
STATS_RULES_TREE_EXPANDED_0 = "statsDialog/rules_tree_0_expanded"
|
||||
STATS_RULES_TREE_EXPANDED_1 = "statsDialog/rules_tree_1_expanded"
|
||||
STATS_RULES_SPLITTER_POS = "statsDialog/rules_splitter_pos"
|
||||
|
|
296
ui/opensnitch/customwidgets/firewalltableview.py
Normal file
296
ui/opensnitch/customwidgets/firewalltableview.py
Normal file
|
@ -0,0 +1,296 @@
|
|||
|
||||
from PyQt5 import Qt, QtCore
|
||||
from PyQt5.QtGui import QStandardItemModel, QStandardItem
|
||||
from PyQt5.QtSql import QSqlQuery, QSqlError
|
||||
from PyQt5.QtWidgets import QTableView, QAbstractSlider, QItemDelegate, QAbstractItemView, QPushButton, QWidget, QVBoxLayout
|
||||
from PyQt5.QtCore import QItemSelectionModel, pyqtSignal, pyqtSlot, QEvent
|
||||
from PyQt5.QtCore import QCoreApplication as QC
|
||||
import math
|
||||
|
||||
from opensnitch.nodes import Nodes
|
||||
from opensnitch.firewall import Firewall
|
||||
from opensnitch.customwidgets.updownbtndelegate import UpDownButtonDelegate
|
||||
|
||||
class FirewallTableModel(QStandardItemModel):
|
||||
rowCountChanged = pyqtSignal()
|
||||
columnCountChanged = pyqtSignal(int)
|
||||
rowsUpdated = pyqtSignal(int, tuple)
|
||||
rowsReordered = pyqtSignal(int, str, str, int, int) # filter, addr, key, old_pos, new_pos
|
||||
|
||||
tableName = ""
|
||||
# total row count which must de displayed in the view
|
||||
totalRowCount = 0
|
||||
# last column count to compare against with
|
||||
lastColumnCount = 0
|
||||
|
||||
FILTER_ALL = 0
|
||||
FILTER_BY_NODE = 1
|
||||
FILTER_BY_TABLE = 2
|
||||
FILTER_BY_CHAIN = 3
|
||||
FILTER_BY_QUERY = 4
|
||||
activeFilter = FILTER_ALL
|
||||
|
||||
UP_BTN = -1
|
||||
DOWN_BTN = 1
|
||||
|
||||
COL_BTNS = 0
|
||||
COL_UUID = 1
|
||||
COL_ADDR = 2
|
||||
COL_CHAIN_NAME = 3
|
||||
COL_CHAIN_TABLE = 4
|
||||
COL_CHAIN_FAMILY = 5
|
||||
COL_CHAIN_HOOK = 6
|
||||
|
||||
headersAll = [
|
||||
"", # buttons
|
||||
"", # uuid
|
||||
QC.translate("firewall", "Node", ""),
|
||||
QC.translate("firewall", "Name", ""),
|
||||
QC.translate("firewall", "Table", ""),
|
||||
QC.translate("firewall", "Family", ""),
|
||||
QC.translate("firewall", "Hook", ""),
|
||||
QC.translate("firewall", "Enabled", ""),
|
||||
QC.translate("firewall", "Description", ""),
|
||||
QC.translate("firewall", "Parameters", ""),
|
||||
QC.translate("firewall", "Action", ""),
|
||||
]
|
||||
|
||||
items = []
|
||||
lastRules = []
|
||||
position = 0
|
||||
|
||||
def __init__(self, tableName):
|
||||
self.tableName = tableName
|
||||
self._nodes = Nodes.instance()
|
||||
self._fw = Firewall.instance()
|
||||
self.lastColumnCount = len(self.headersAll)
|
||||
self.lastQueryArgs = ()
|
||||
|
||||
QStandardItemModel.__init__(self, 0, self.lastColumnCount)
|
||||
self.setHorizontalHeaderLabels(self.headersAll)
|
||||
|
||||
def filterByNode(self, addr):
|
||||
self.activeFilter = self.FILTER_BY_NODE
|
||||
self.fillRows(0, True, addr)
|
||||
|
||||
def filterAll(self):
|
||||
self.activeFilter = self.FILTER_ALL
|
||||
self.fillRows(0, True)
|
||||
|
||||
def filterByTable(self, addr, name, family):
|
||||
self.activeFilter = self.FILTER_BY_TABLE
|
||||
self.fillRows(0, True, addr, name, family)
|
||||
|
||||
def filterByChain(self, addr, table, family, chain, hook):
|
||||
self.activeFilter = self.FILTER_BY_CHAIN
|
||||
self.fillRows(0, True, addr, table, family, chain, hook)
|
||||
|
||||
def filterByQuery(self, query):
|
||||
self.activeFilter = self.FILTER_BY_QUERY
|
||||
self.fillRows(0, True, query)
|
||||
|
||||
def reorderRows(self, action, row):
|
||||
if (row.row()+action == self.rowCount() and action == self.DOWN_BTN) or \
|
||||
(row.row() == 0 and action == self.UP_BTN):
|
||||
return
|
||||
|
||||
# XXX: better use moveRow()?
|
||||
newRow = []
|
||||
# save the row we're about to overwrite
|
||||
for c in range(self.columnCount()):
|
||||
item = self.index(row.row()+action, c)
|
||||
itemText = item.data()
|
||||
newRow.append(itemText)
|
||||
# overwrite next item with current data
|
||||
for c in range(self.columnCount()):
|
||||
curItem = self.index(row.row(), c).data()
|
||||
nextIdx = self.index(row.row()+action, c)
|
||||
self.setData(nextIdx, curItem, QtCore.Qt.DisplayRole)
|
||||
# restore row with the overwritten data
|
||||
for i, nr in enumerate(newRow):
|
||||
idx = self.index(row.row(), i)
|
||||
self.setData(idx, nr, QtCore.Qt.DisplayRole)
|
||||
|
||||
self.rowsReordered.emit(
|
||||
self.activeFilter,
|
||||
self.index(row.row()+action, self.COL_ADDR).data(), # address
|
||||
self.index(row.row()+action, self.COL_UUID).data(), # key
|
||||
row.row(),
|
||||
row.row()+action)
|
||||
|
||||
def refresh(self, force=False):
|
||||
self.fillRows(0, force, *self.lastQueryArgs)
|
||||
|
||||
#Some QSqlQueryModel methods must be mimiced so that this class can serve as a drop-in replacement
|
||||
#mimic QSqlQueryModel.query()
|
||||
def query(self):
|
||||
return self
|
||||
|
||||
#mimic QSqlQueryModel.query().lastError()
|
||||
def lastError(self):
|
||||
return QSqlError()
|
||||
|
||||
#mimic QSqlQueryModel.clear()
|
||||
def clear(self):
|
||||
self.items = []
|
||||
self.removeColumns(0, self.lastColumnCount)
|
||||
self.setColumnCount(0)
|
||||
self.setRowCount(0)
|
||||
|
||||
# set columns based on query's fields
|
||||
def setModelColumns(self, headers):
|
||||
count = len(headers)
|
||||
self.clear()
|
||||
self.setHorizontalHeaderLabels(headers)
|
||||
self.lastColumnCount = count
|
||||
self.setColumnCount(self.lastColumnCount)
|
||||
self.columnCountChanged.emit(count)
|
||||
|
||||
def query(self):
|
||||
return QSqlQuery()
|
||||
|
||||
def setQuery(self, q, db, args=None):
|
||||
self.refresh()
|
||||
|
||||
def nextRecord(self, offset):
|
||||
self.position += 1
|
||||
|
||||
def prevRecord(self, offset):
|
||||
self.position -= 1
|
||||
|
||||
def fillRows(self, upperBound, force, *data):
|
||||
if self.activeFilter == self.FILTER_BY_NODE and len(data) == 0:
|
||||
return
|
||||
|
||||
cols = []
|
||||
rules = []
|
||||
#don't trigger setItem's signals for each cell, instead emit dataChanged for all cells
|
||||
self.blockSignals(True)
|
||||
# mandatory for rows refreshing
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
if self.activeFilter == self.FILTER_BY_NODE:
|
||||
rules = self._fw.get_node_rules(data[0])
|
||||
self.setModelColumns(self.headersAll)
|
||||
elif self.activeFilter == self.FILTER_BY_TABLE:
|
||||
rules = self._fw.filter_by_table(data[0], data[1], data[2])
|
||||
self.setModelColumns(self.headersAll)
|
||||
elif self.activeFilter == self.FILTER_BY_CHAIN:
|
||||
rules = self._fw.filter_by_chain(data[0], data[1], data[2], data[3], data[4])
|
||||
self.setModelColumns(self.headersAll)
|
||||
elif self.activeFilter == self.FILTER_BY_QUERY:
|
||||
rules = self._fw.filter_rules(data[0])
|
||||
self.setModelColumns(self.headersAll)
|
||||
else:
|
||||
self.setModelColumns(self.headersAll)
|
||||
rules = self._fw.get_rules()
|
||||
|
||||
self.addRows(rules)
|
||||
|
||||
self.blockSignals(False)
|
||||
if self.lastRules != rules or force == True:
|
||||
self.layoutChanged.emit()
|
||||
self.totalRowCount = len(rules)
|
||||
self.setRowCount(self.totalRowCount)
|
||||
self.rowsUpdated.emit(self.activeFilter, data)
|
||||
self.dataChanged.emit(self.createIndex(0,0), self.createIndex(self.rowCount(), self.columnCount()))
|
||||
|
||||
self.lastRules = rules
|
||||
self.lastQueryArgs = data
|
||||
del cols
|
||||
del rules
|
||||
|
||||
def addRows(self, rules):
|
||||
self.items = []
|
||||
for rows in rules:
|
||||
cols = []
|
||||
cols.append(QStandardItem("")) # buttons column
|
||||
for cl in rows:
|
||||
item = QStandardItem(cl)
|
||||
item.setData(cl, QtCore.Qt.UserRole+1)
|
||||
cols.append(item)
|
||||
self.appendRow(cols)
|
||||
|
||||
class FirewallTableView(QTableView):
|
||||
# how many rows can potentially be displayed in viewport
|
||||
# the actual number of rows currently displayed may be less than this
|
||||
maxRowsInViewport = 0
|
||||
rowsReordered = pyqtSignal(str) # addr
|
||||
|
||||
def __init__(self, parent):
|
||||
QTableView.__init__(self, parent)
|
||||
self._fw = Firewall.instance()
|
||||
self._fw.rules.rulesUpdated.connect(self._cb_fw_rules_updated)
|
||||
|
||||
self.verticalHeader().setVisible(True)
|
||||
self.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignCenter)
|
||||
self.horizontalHeader().setStretchLastSection(True)
|
||||
|
||||
# FIXME: if the firewall being used is iptables, hide the column to
|
||||
# reorder rules, it's not supported.
|
||||
updownBtn = UpDownButtonDelegate(self)
|
||||
self.setItemDelegateForColumn(0, updownBtn)
|
||||
updownBtn.clicked.connect(self._cb_fw_rule_position_changed)
|
||||
|
||||
def _cb_fw_rules_updated(self):
|
||||
self.model().refresh(True)
|
||||
|
||||
def _cb_column_count_changed(self, num):
|
||||
for i in range(num):
|
||||
self.resizeColumnToContents(i)
|
||||
|
||||
def _cb_fw_rule_position_changed(self, action, row):
|
||||
self.model().reorderRows(action, row)
|
||||
|
||||
def _cb_rows_reordered(self, view, node_addr, uuid, old_pos, new_pos):
|
||||
if self._fw.swap_rules(view, node_addr, uuid, old_pos, new_pos):
|
||||
self.rowsReordered.emit(node_addr)
|
||||
|
||||
#@QtCore.pyqtSlot(int, tuple)
|
||||
def _cb_rows_updated(self, view, data):
|
||||
for c in range(self.model().rowCount()):
|
||||
self.setColumnHidden(c, False)
|
||||
#self.horizontalHeader().setSectionResizeMode(
|
||||
# c, QHeaderView.ResizeToContents
|
||||
#)
|
||||
|
||||
self.setColumnHidden(FirewallTableModel.COL_BTNS, True)
|
||||
self.setColumnHidden(FirewallTableModel.COL_UUID, True)
|
||||
if view >= self.model().FILTER_BY_NODE:
|
||||
# hide address column
|
||||
self.setColumnHidden(FirewallTableModel.COL_ADDR, True)
|
||||
if view >= self.model().FILTER_BY_TABLE:
|
||||
self.setColumnHidden(FirewallTableModel.COL_CHAIN_TABLE, True)
|
||||
self.setColumnHidden(FirewallTableModel.COL_CHAIN_FAMILY, True)
|
||||
if view >= self.model().FILTER_BY_CHAIN:
|
||||
# hide chain's name, family and hook
|
||||
self.setColumnHidden(FirewallTableModel.COL_CHAIN_NAME, True)
|
||||
self.setColumnHidden(FirewallTableModel.COL_CHAIN_HOOK, True)
|
||||
self.setColumnHidden(FirewallTableModel.COL_BTNS, False)
|
||||
|
||||
def filterAll(self):
|
||||
self.model().filterAll()
|
||||
|
||||
def filterByNode(self, addr):
|
||||
self.model().filterByNode(addr)
|
||||
|
||||
def filterByTable(self, addr, name, family):
|
||||
self.model().filterByTable(addr, name, family)
|
||||
|
||||
def filterByChain(self, addr, table, family, chain, hook):
|
||||
self.model().filterByChain(addr, table, family, chain, hook)
|
||||
|
||||
def filterByQuery(self, query):
|
||||
self.model().filterByQuery(query)
|
||||
|
||||
def refresh(self):
|
||||
self.model().refresh(True)
|
||||
|
||||
def setModel(self, model):
|
||||
super().setModel(model)
|
||||
self.horizontalHeader().sortIndicatorChanged.disconnect()
|
||||
self.setSortingEnabled(True)
|
||||
self.model().columnCountChanged.connect(self._cb_column_count_changed)
|
||||
model.rowsUpdated.connect(self._cb_rows_updated)
|
||||
model.rowsReordered.connect(self._cb_rows_reordered)
|
||||
|
53
ui/opensnitch/customwidgets/updownbtndelegate.py
Normal file
53
ui/opensnitch/customwidgets/updownbtndelegate.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from PyQt5 import Qt, QtCore
|
||||
from PyQt5.QtGui import QRegion
|
||||
from PyQt5.QtWidgets import QItemDelegate, QAbstractItemView, QPushButton, QWidget, QVBoxLayout, QSizePolicy
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
|
||||
class UpDownButtonDelegate(QItemDelegate):
|
||||
clicked = pyqtSignal(int, QtCore.QModelIndex)
|
||||
|
||||
UP=-1
|
||||
DOWN=1
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
if (
|
||||
isinstance(self.parent(), QAbstractItemView)
|
||||
and self.parent().model() is index.model()
|
||||
):
|
||||
self.parent().openPersistentEditor(index)
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
w = QWidget(parent)
|
||||
w.setContentsMargins(0, 0, 0, 0)
|
||||
w.setAutoFillBackground(True)
|
||||
|
||||
layout = QVBoxLayout(w)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
btnUp = QPushButton(parent)
|
||||
btnUp.setText("⇡")
|
||||
btnUp.setFlat(True)
|
||||
btnUp.clicked.connect(lambda: self._cb_button_clicked(self.UP, index))
|
||||
|
||||
btnDown = QPushButton(parent)
|
||||
btnDown.setText("⇣")
|
||||
btnDown.setFlat(True)
|
||||
btnDown.clicked.connect(lambda: self._cb_button_clicked(self.DOWN, index))
|
||||
|
||||
layout.addWidget(btnUp)
|
||||
layout.addWidget(btnDown)
|
||||
return w
|
||||
|
||||
def _cb_button_clicked(self, action, idx):
|
||||
self.clicked.emit(action, idx)
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
rect = QtCore.QRect(option.rect)
|
||||
minWidth = editor.minimumSizeHint().width()
|
||||
if rect.width() < minWidth:
|
||||
rect.setWidth(minWidth)
|
||||
editor.setGeometry(rect)
|
||||
# create a new mask based on the option rectangle, then apply it
|
||||
mask = QRegion(0, 0, option.rect.width(), option.rect.height())
|
||||
editor.setProperty('offMask', mask)
|
||||
editor.setMask(mask)
|
266
ui/opensnitch/dialogs/firewall.py
Normal file
266
ui/opensnitch/dialogs/firewall.py
Normal file
|
@ -0,0 +1,266 @@
|
|||
import sys
|
||||
import time
|
||||
import os
|
||||
import os.path
|
||||
import json
|
||||
|
||||
from PyQt5 import QtCore, QtGui, uic, QtWidgets
|
||||
from PyQt5.QtCore import QCoreApplication as QC
|
||||
|
||||
from opensnitch.config import Config
|
||||
from opensnitch.nodes import Nodes
|
||||
from opensnitch.dialogs.firewall_rule import FwRuleDialog
|
||||
from opensnitch import ui_pb2
|
||||
import opensnitch.firewall as Fw
|
||||
import opensnitch.firewall.profiles as FwProfiles
|
||||
|
||||
|
||||
DIALOG_UI_PATH = "%s/../res/firewall.ui" % os.path.dirname(sys.modules[__name__].__file__)
|
||||
class FirewallDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
||||
LOG_TAG = "[fw dialog]"
|
||||
|
||||
COMBO_IN = 0
|
||||
COMBO_OUT = 1
|
||||
|
||||
_notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
|
||||
|
||||
def __init__(self, parent=None, appicon=None, node=None):
|
||||
QtWidgets.QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
self.setWindowIcon(appicon)
|
||||
self.appicon = appicon
|
||||
|
||||
# TODO: profiles are ready to be used. They need to be tested, and
|
||||
# create some default profiles (home, office, public, ...)
|
||||
self.comboProfile.setVisible(False)
|
||||
self.lblProfile.setVisible(False)
|
||||
|
||||
self.secHighIcon = QtGui.QIcon.fromTheme("security-high")
|
||||
self.secMediumIcon = QtGui.QIcon.fromTheme("security-medium")
|
||||
self.secLowIcon = QtGui.QIcon.fromTheme("security-low")
|
||||
self.lblStatusIcon.setPixmap( self.secHighIcon.pixmap(96,96) );
|
||||
|
||||
self._fwrule_dialog = FwRuleDialog(appicon=self.appicon)
|
||||
self._cfg = Config.get()
|
||||
self._fw = Fw.Firewall.instance()
|
||||
self._nodes = Nodes.instance()
|
||||
self._fw_profiles = {}
|
||||
|
||||
self._notification_callback.connect(self._cb_notification_callback)
|
||||
self._notifications_sent = {}
|
||||
|
||||
self._nodes.nodesUpdated.connect(self._cb_nodes_updated)
|
||||
self.cmdNewRule.clicked.connect(self._cb_new_rule_clicked)
|
||||
self.cmdExcludeService.clicked.connect(self._cb_exclude_service_clicked)
|
||||
self.comboInput.currentIndexChanged.connect(lambda: self._cb_combo_policy_changed(self.COMBO_IN))
|
||||
self.comboOutput.currentIndexChanged.connect(lambda: self._cb_combo_policy_changed(self.COMBO_OUT))
|
||||
self.comboProfile.currentIndexChanged.connect(self._cb_combo_profile_changed)
|
||||
self.sliderFwEnable.valueChanged.connect(self._cb_enable_fw_changed)
|
||||
self.cmdClose.clicked.connect(self._cb_close_clicked)
|
||||
|
||||
@QtCore.pyqtSlot(ui_pb2.NotificationReply)
|
||||
def _cb_notification_callback(self, reply):
|
||||
if reply.id in self._notifications_sent:
|
||||
if reply.code == ui_pb2.OK:
|
||||
rep = self._notifications_sent[reply.id]
|
||||
self._set_status_successful(QC.translate("firewall", "Configuration applied."))
|
||||
|
||||
else:
|
||||
self._set_status_error(QC.translate("firewall", "Error: {0}").format(reply.data))
|
||||
|
||||
del self._notifications_sent[reply.id]
|
||||
else:
|
||||
print(self.LOG_TAG, "unknown notification:", reply)
|
||||
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def _cb_nodes_updated(self, total):
|
||||
self._check_fw_status()
|
||||
|
||||
def _cb_combo_profile_changed(self, idx):
|
||||
combo_profile = self._fw_profiles[idx]
|
||||
json_profile = json.dumps(list(combo_profile.values())[0]['Profile'])
|
||||
|
||||
for addr in self._nodes.get():
|
||||
fwcfg = self._nodes.get_node(addr)['firewall']
|
||||
ok, err = self._fw.apply_profile(addr, json_profile)
|
||||
if ok:
|
||||
self.send_notification(addr, fwcfg)
|
||||
else:
|
||||
self._set_status_error(QC.translate("firewall", "error adding profile extra rules:", err))
|
||||
|
||||
def _cb_combo_policy_changed(self, combo):
|
||||
wantedProfile = FwProfiles.ProfileAcceptInput.value
|
||||
if combo == self.COMBO_OUT:
|
||||
wantedProfile = FwProfiles.ProfileAcceptOutput.value
|
||||
if self.comboOutput.currentIndex() == 1:
|
||||
wantedProfile = FwProfiles.ProfileDropOutput.value
|
||||
else:
|
||||
if self.comboInput.currentIndex() == 1:
|
||||
wantedProfile = FwProfiles.ProfileDropInput.value
|
||||
|
||||
for addr in self._nodes.get():
|
||||
fwcfg = self._nodes.get_node(addr)['firewall']
|
||||
|
||||
json_profile = json.dumps(wantedProfile)
|
||||
ok, err = self._fw.apply_profile(addr, json_profile)
|
||||
if ok:
|
||||
self.send_notification(addr, fwcfg)
|
||||
else:
|
||||
self._set_status_error(QC.translate("firewall", "Policy not applied: {0}".format(err)))
|
||||
|
||||
def _cb_new_rule_clicked(self):
|
||||
self.new_rule()
|
||||
|
||||
def _cb_exclude_service_clicked(self):
|
||||
self.exclude_service()
|
||||
|
||||
def _cb_enable_fw_changed(self, enable):
|
||||
if enable:
|
||||
self._set_status_message(QC.translate("firewall", "Enabling firewall..."))
|
||||
else:
|
||||
self._set_status_message(QC.translate("firewall", "Disabling firewall..."))
|
||||
|
||||
for addr in self._nodes.get():
|
||||
fwcfg = self._nodes.get_node(addr)['firewall']
|
||||
fwcfg.Enabled = True if enable else False
|
||||
self.send_notification(addr, fwcfg)
|
||||
|
||||
self.lblStatusIcon.setEnabled(enable)
|
||||
self.policiesBox.setEnabled(enable)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
def _cb_close_clicked(self):
|
||||
self._close()
|
||||
|
||||
def _load_nodes(self):
|
||||
self._nodes = self._nodes.get()
|
||||
|
||||
def _close(self):
|
||||
self.hide()
|
||||
|
||||
def showEvent(self, event):
|
||||
super(FirewallDialog, self).showEvent(event)
|
||||
self._reset_fields()
|
||||
self._check_fw_status()
|
||||
self._fw_profiles = FwProfiles.Profiles.load_predefined_profiles()
|
||||
self.comboProfile.blockSignals(True)
|
||||
for pr in self._fw_profiles:
|
||||
self.comboProfile.addItem([pr[k] for k in pr][0]['Name'])
|
||||
self.comboProfile.blockSignals(False)
|
||||
|
||||
def send_notification(self, node_addr, fw_config):
|
||||
self._set_status_message(QC.translate("firewall", "Applying changes..."))
|
||||
nid, notif = self._nodes.reload_fw(node_addr, fw_config, self._notification_callback)
|
||||
self._notifications_sent[nid] = {'addr': node_addr, 'notif': notif}
|
||||
|
||||
def _check_fw_status(self):
|
||||
self.lblFwStatus.setText("")
|
||||
self.sliderFwEnable.blockSignals(True)
|
||||
self.comboInput.blockSignals(True)
|
||||
self.comboOutput.blockSignals(True)
|
||||
self.comboProfile.blockSignals(True)
|
||||
|
||||
if self._nodes.count() == 0:
|
||||
self._disable_widgets()
|
||||
return
|
||||
|
||||
# TODO: handle nodes' firewall properly
|
||||
enableFw = False
|
||||
for addr in self._nodes.get():
|
||||
self._fwConfig = self._nodes.get_node(addr)['firewall']
|
||||
enableFw |= self._fwConfig.Enabled
|
||||
|
||||
n = self._nodes.get_node(addr)
|
||||
j = json.loads(n['data'].config)
|
||||
|
||||
if j['Firewall'] == "iptables":
|
||||
self._disable_widgets()
|
||||
self.lblFwStatus.setText(
|
||||
QC.translate("firewall",
|
||||
"OpenSnitch is using 'iptables' as firewall, but it's not configurable from the GUI.\n"
|
||||
"Set 'Firewall' option to 'nftables' in /etc/opensnitchd/default-config.json \n"
|
||||
"if you want to configure firewall rules from the GUI."
|
||||
))
|
||||
return
|
||||
if n['data'].systemFirewall.Version == 0:
|
||||
self._disable_widgets()
|
||||
self.lblFwStatus.setText(
|
||||
QC.translate("firewall", "<html>The firewall configuration is outdated,\n"
|
||||
"you need to update it to the new format: <a href=\""+ Config.HELP_SYS_RULES_URL + "\">learn more</a>"
|
||||
"</html>"
|
||||
))
|
||||
return
|
||||
|
||||
# XXX: Here we loop twice over the chains. We could have 1 loop.
|
||||
pol_in = self._fw.chains.get_policy(addr, Fw.Hooks.INPUT.value)
|
||||
pol_out = self._fw.chains.get_policy(addr, Fw.Hooks.OUTPUT.value)
|
||||
|
||||
if pol_in != None:
|
||||
self.comboInput.setCurrentIndex(
|
||||
Fw.Policy.values().index(pol_in)
|
||||
)
|
||||
else:
|
||||
self._set_status_error(QC.translate("firewall", "Error getting INPUT chain policy"))
|
||||
self._disable_widgets()
|
||||
if pol_out != None:
|
||||
self.comboOutput.setCurrentIndex(
|
||||
Fw.Policy.values().index(pol_out)
|
||||
)
|
||||
else:
|
||||
self._set_status_error(QC.translate("firewall", "Error getting OUTPUT chain policy"))
|
||||
self._disable_widgets()
|
||||
|
||||
|
||||
# some nodes may have the firewall disabled whilst other enabled
|
||||
#if not enableFw:
|
||||
# self.lblFwStatus(QC.translate("firewall", "Some nodes have the firewall disabled"))
|
||||
|
||||
self._disable_widgets(False)
|
||||
self.lblStatusIcon.setEnabled(enableFw)
|
||||
self.sliderFwEnable.setValue(enableFw)
|
||||
self.sliderFwEnable.blockSignals(False)
|
||||
self.comboInput.blockSignals(False)
|
||||
self.comboOutput.blockSignals(False)
|
||||
self.comboProfile.blockSignals(False)
|
||||
|
||||
|
||||
|
||||
def load_rule(self, addr, uuid):
|
||||
self._fwrule_dialog.load(addr, uuid)
|
||||
|
||||
def new_rule(self):
|
||||
self._fwrule_dialog.new()
|
||||
|
||||
def exclude_service(self):
|
||||
self._fwrule_dialog.exclude_service()
|
||||
|
||||
def _set_status_error(self, msg):
|
||||
self.statusLabel.show()
|
||||
self.statusLabel.setStyleSheet('color: red')
|
||||
self.statusLabel.setText(msg)
|
||||
|
||||
def _set_status_successful(self, msg):
|
||||
self.statusLabel.show()
|
||||
self.statusLabel.setStyleSheet('color: green')
|
||||
self.statusLabel.setText(msg)
|
||||
|
||||
def _set_status_message(self, msg):
|
||||
self.statusLabel.show()
|
||||
self.statusLabel.setStyleSheet('color: darkorange')
|
||||
self.statusLabel.setText(msg)
|
||||
|
||||
def _reset_status_message(self):
|
||||
self.statusLabel.setText("")
|
||||
self.statusLabel.hide()
|
||||
|
||||
def _reset_fields(self):
|
||||
self._reset_status_message()
|
||||
|
||||
def _disable_widgets(self, disable=True):
|
||||
self.sliderFwEnable.setEnabled(not disable)
|
||||
self.comboInput.setEnabled(not disable)
|
||||
self.comboOutput.setEnabled(not disable)
|
||||
self.cmdNewRule.setEnabled(not disable)
|
||||
self.cmdExcludeService.setEnabled(not disable)
|
401
ui/opensnitch/dialogs/firewall_rule.py
Normal file
401
ui/opensnitch/dialogs/firewall_rule.py
Normal file
|
@ -0,0 +1,401 @@
|
|||
import sys
|
||||
import os
|
||||
import os.path
|
||||
|
||||
from PyQt5 import QtCore, uic, QtWidgets
|
||||
from PyQt5.QtCore import QCoreApplication as QC
|
||||
|
||||
from opensnitch.nodes import Nodes
|
||||
from opensnitch.utils import NetworkServices, QuickHelp
|
||||
from opensnitch import ui_pb2
|
||||
import opensnitch.firewall as Fw
|
||||
from opensnitch.firewall.utils import Utils as FwUtils
|
||||
|
||||
|
||||
DIALOG_UI_PATH = "%s/../res/firewall_rule.ui" % os.path.dirname(sys.modules[__name__].__file__)
|
||||
class FwRuleDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
||||
LOG_TAG = "[fw rule dialog]"
|
||||
ACTION_IDX_DENY = 0
|
||||
ACTION_IDX_ALLOW = 1
|
||||
|
||||
IN = 0
|
||||
OUT = 1
|
||||
|
||||
OP_NEW = 0
|
||||
OP_SAVE = 1
|
||||
OP_DELETE = 2
|
||||
|
||||
FORM_TYPE_SIMPLE = 0
|
||||
FORM_TYPE_EXCLUDE_SERVICE = 1
|
||||
FORM_TYPE = FORM_TYPE_SIMPLE
|
||||
|
||||
_notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
|
||||
|
||||
def __init__(self, parent=None, appicon=None):
|
||||
QtWidgets.QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
self.setWindowIcon(appicon)
|
||||
# Other interesting flags: QtCore.Qt.Tool | QtCore.Qt.BypassWindowManagerHint
|
||||
self._fw = Fw.Firewall.instance()
|
||||
self._nodes = Nodes.instance()
|
||||
|
||||
self._notification_callback.connect(self._cb_notification_callback)
|
||||
self._notifications_sent = {}
|
||||
|
||||
self.uuid = ""
|
||||
self.simple_port_idx = None
|
||||
|
||||
self._nodes.nodesUpdated.connect(self._cb_nodes_updated)
|
||||
self.cmdClose.clicked.connect(self._cb_close_clicked)
|
||||
self.cmdAdd.clicked.connect(self._cb_add_clicked)
|
||||
self.cmdSave.clicked.connect(self._cb_save_clicked)
|
||||
self.cmdDelete.clicked.connect(self._cb_delete_clicked)
|
||||
self.helpButton.clicked.connect(self._cb_help_button_clicked)
|
||||
|
||||
self.net_srv = NetworkServices()
|
||||
self.comboPorts.addItems(self.net_srv.to_array())
|
||||
self.comboPorts.currentIndexChanged.connect(self._cb_combo_ports_index_changed)
|
||||
|
||||
def showEvent(self, event):
|
||||
super(FwRuleDialog, self).showEvent(event)
|
||||
self._reset_fields()
|
||||
|
||||
if FwUtils.isProtobufSupported() == False:
|
||||
self._disable_controls()
|
||||
self._disable_buttons()
|
||||
self._set_status_error(
|
||||
QC.translate(
|
||||
"firewall",
|
||||
"Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior\n(pip3 install --ignore-installed protobuf==3.8.0"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self._load_nodes()
|
||||
|
||||
@QtCore.pyqtSlot(ui_pb2.NotificationReply)
|
||||
def _cb_notification_callback(self, reply):
|
||||
self._enable_buttons()
|
||||
|
||||
if reply.id in self._notifications_sent:
|
||||
if reply.code == ui_pb2.OK:
|
||||
rep = self._notifications_sent[reply.id]
|
||||
if 'operation' in rep and rep['operation'] == self.OP_DELETE:
|
||||
self.tabWidget.setDisabled(True)
|
||||
self._set_status_successful(QC.translate("firewall", "Rule deleted"))
|
||||
self._disable_controls()
|
||||
return
|
||||
|
||||
self._set_status_successful(QC.translate("firewall", "Rule added"))
|
||||
|
||||
else:
|
||||
self._set_status_error(QC.translate("firewall", "Error: {0}").format(reply.data))
|
||||
|
||||
del self._notifications_sent[reply.id]
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def _cb_nodes_updated(self, total):
|
||||
self.tabWidget.setDisabled(True if total == 0 else False)
|
||||
|
||||
def closeEvent(self, e):
|
||||
self._close()
|
||||
|
||||
def _cb_combo_ports_index_changed(self, idx):
|
||||
self.simple_port_idx = idx
|
||||
|
||||
def _cb_help_button_clicked(self):
|
||||
QuickHelp.show(
|
||||
QC.translate("firewall",
|
||||
"You can use ',' or '-' to specify multiple ports or a port range:<br>" \
|
||||
"22 or 22,443 or 50000-60000"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _cb_close_clicked(self):
|
||||
self._close()
|
||||
|
||||
def _cb_delete_clicked(self):
|
||||
node_addr, node, chain = self.form_to_protobuf()
|
||||
if node_addr == None:
|
||||
self._set_status_error(QC.translate("firewall", "Invalid rule, review parameters"))
|
||||
return
|
||||
|
||||
self._set_status_message(QC.translate("firewall", "Deleting rule, wait"))
|
||||
ok, fw_config = self._fw.delete_rule(node_addr, self.uuid)
|
||||
if not ok:
|
||||
self._set_status_error(QC.translate("firewall", "Error updating rule"))
|
||||
return
|
||||
|
||||
self.send_notification(node_addr, node['firewall'], self.OP_DELETE)
|
||||
|
||||
def _cb_save_clicked(self):
|
||||
node_addr, node, chain = self.form_to_protobuf()
|
||||
if node_addr == None:
|
||||
self._set_status_error(QC.translate("firewall", "Invalid rule, review parameters"))
|
||||
return
|
||||
|
||||
self._set_status_message(QC.translate("firewall", "Adding rule, wait"))
|
||||
ok, err = self._fw.update_rule(node_addr, self.uuid, chain)
|
||||
if not ok:
|
||||
self._set_status_error(QC.translate("firewall", "Error updating rule: {0}".format(err)))
|
||||
return
|
||||
|
||||
self._enable_buttons(False)
|
||||
self.send_notification(node_addr, node['firewall'], self.OP_SAVE)
|
||||
|
||||
def _cb_add_clicked(self):
|
||||
node_addr, node, chain = self.form_to_protobuf()
|
||||
if node_addr == None:
|
||||
self._set_status_error(QC.translate("firewall", "Invalid rule, review parameters"))
|
||||
return
|
||||
ok, err = self._fw.insert_rule(node_addr, chain)
|
||||
if not ok:
|
||||
self._set_status_error(QC.translate("firewall", "Error adding rule: {0}".format(err)))
|
||||
return
|
||||
self._set_status_message(QC.translate("firewall", "Adding rule, wait"))
|
||||
self._enable_buttons(False)
|
||||
self.send_notification(node_addr, node['firewall'], self.OP_NEW)
|
||||
|
||||
def _close(self):
|
||||
self.hide()
|
||||
|
||||
def _load_nodes(self):
|
||||
self.comboNodes.clear()
|
||||
self._node_list = self._nodes.get()
|
||||
for addr in self._node_list:
|
||||
self.comboNodes.addItem(addr)
|
||||
|
||||
if len(self._node_list) == 0:
|
||||
self.tabWidget.setDisabled(True)
|
||||
|
||||
def load(self, addr, uuid):
|
||||
self.show()
|
||||
|
||||
self.FORM_TYPE = self.FORM_TYPE_SIMPLE
|
||||
self.setWindowTitle(QC.translate("firewall", "Firewall rule"))
|
||||
self.cmdDelete.setVisible(True)
|
||||
self.cmdSave.setVisible(True)
|
||||
self.cmdAdd.setVisible(False)
|
||||
self.checkEnable.setVisible(True)
|
||||
self.checkEnable.setEnabled(True)
|
||||
self.checkEnable.setChecked(True)
|
||||
self.comboOperator.setVisible(True)
|
||||
self.comboOperator.setCurrentIndex(0)
|
||||
self.frameDirection.setVisible(True)
|
||||
self.frameAction.setVisible(True)
|
||||
|
||||
self.cmdSave.setVisible(False)
|
||||
self.cmdDelete.setVisible(False)
|
||||
self.cmdAdd.setVisible(True)
|
||||
|
||||
|
||||
self.cmdSave.setVisible(True)
|
||||
self.cmdAdd.setVisible(False)
|
||||
self.cmdDelete.setVisible(True)
|
||||
self.show()
|
||||
|
||||
self.uuid = uuid
|
||||
|
||||
node, rule = self._fw.get_rule_by_uuid(uuid)
|
||||
# TODO: implement complex rules
|
||||
if rule == None or \
|
||||
(rule.Hook.lower() != Fw.Hooks.INPUT.value and rule.Hook.lower() != Fw.Hooks.OUTPUT.value):
|
||||
hook = "invalid" if rule == None else rule.Hook
|
||||
self._set_status_error(QC.translate("firewall", "Rule type ({0}) not supported yet".format(hook)))
|
||||
self._disable_controls()
|
||||
return
|
||||
if len(rule.Rules[0].Expressions) > 1:
|
||||
self._set_status_error(QC.translate("firewall", "Complex rules types not supported yet"))
|
||||
self._disable_controls()
|
||||
return
|
||||
|
||||
self.checkEnable.setChecked(rule.Rules[0].Enabled)
|
||||
self.lineDescription.setText(rule.Rules[0].Description)
|
||||
|
||||
# TODO: support complex expressions: tcp dport 22 ip daddr != 127.0.0.1
|
||||
isNotSupported = True
|
||||
for exp in rule.Rules[0].Expressions:
|
||||
if Fw.Utils.isExprPort(exp.Statement.Name):
|
||||
try:
|
||||
self.comboPorts.setCurrentIndex(
|
||||
self.net_srv.index_by_port(exp.Statement.Values[0].Value)
|
||||
)
|
||||
except:
|
||||
self.comboPorts.setCurrentText(exp.Statement.Values[0].Value)
|
||||
|
||||
op = Fw.Operator.EQUAL.value if exp.Statement.Op == "" else exp.Statement.Op
|
||||
self.comboOperator.setCurrentIndex(
|
||||
Fw.Operator.values().index(op)
|
||||
)
|
||||
isNotSupported = False
|
||||
break
|
||||
if isNotSupported:
|
||||
self._set_status_error(QC.translate("firewall", "Only port rules can be edited for now."))
|
||||
self._disable_controls()
|
||||
return
|
||||
|
||||
if rule.Hook.lower() == Fw.Hooks.INPUT.value:
|
||||
self.comboDirection.setCurrentIndex(0)
|
||||
else:
|
||||
self.comboDirection.setCurrentIndex(1)
|
||||
# TODO: changing the direction of an existed rule needs work, it causes
|
||||
# some nasty effects. Disabled for now.
|
||||
self.comboDirection.setEnabled(False)
|
||||
|
||||
self.comboVerdict.setCurrentIndex(
|
||||
Fw.Verdicts.values().index(
|
||||
rule.Rules[0].Target.lower()
|
||||
)-1
|
||||
)
|
||||
|
||||
def new(self):
|
||||
self.show()
|
||||
|
||||
self.FORM_TYPE = self.FORM_TYPE_SIMPLE
|
||||
self.setWindowTitle(QC.translate("firewall", "Firewall rule"))
|
||||
self.cmdDelete.setVisible(False)
|
||||
self.cmdSave.setVisible(False)
|
||||
self.cmdAdd.setVisible(True)
|
||||
self.checkEnable.setVisible(True)
|
||||
self.checkEnable.setEnabled(True)
|
||||
self.checkEnable.setChecked(True)
|
||||
self.comboOperator.setVisible(True)
|
||||
self.comboOperator.setCurrentIndex(0)
|
||||
self.frameDirection.setVisible(True)
|
||||
self.frameAction.setVisible(True)
|
||||
|
||||
self.cmdSave.setVisible(False)
|
||||
self.cmdDelete.setVisible(False)
|
||||
self.cmdAdd.setVisible(True)
|
||||
|
||||
def exclude_service(self):
|
||||
self.show()
|
||||
|
||||
self.FORM_TYPE = self.FORM_TYPE_EXCLUDE_SERVICE
|
||||
self.setWindowTitle(QC.translate("firewall", "Exclude service"))
|
||||
self.cmdDelete.setVisible(False)
|
||||
self.cmdSave.setVisible(False)
|
||||
self.cmdAdd.setVisible(True)
|
||||
self.checkEnable.setVisible(False)
|
||||
self.checkEnable.setEnabled(True)
|
||||
self.comboOperator.setVisible(False)
|
||||
self.comboOperator.setCurrentIndex(0)
|
||||
self.frameDirection.setVisible(False)
|
||||
self.frameAction.setVisible(False)
|
||||
|
||||
self.checkEnable.setChecked(True)
|
||||
|
||||
def form_to_protobuf(self):
|
||||
"""Transform form widgets to protouf struct
|
||||
"""
|
||||
chain = Fw.ChainFilter.input()
|
||||
|
||||
# output rules must be placed under mangle table and before
|
||||
# interception rules. Otherwise we'd intercept them.
|
||||
if self.comboDirection.currentIndex() == self.OUT or self.FORM_TYPE == self.FORM_TYPE_EXCLUDE_SERVICE:
|
||||
chain = Fw.ChainMangle.output()
|
||||
|
||||
rule = Fw.Rules.new(
|
||||
enabled=self.checkEnable.isChecked(),
|
||||
_uuid=self.uuid,
|
||||
description=self.lineDescription.text(),
|
||||
target=Fw.Verdicts.values()[self.comboVerdict.currentIndex()+1] # index 0 is ""
|
||||
)
|
||||
|
||||
if self.comboPorts.currentText() != "":
|
||||
portValue = "0"
|
||||
try:
|
||||
if "," in self.comboPorts.currentText() or "-" in self.comboPorts.currentText():
|
||||
raise ValueError("port entered is multiport or a port range")
|
||||
if self.simple_port_idx == None:
|
||||
raise ValueError("user didn't select a port from the list")
|
||||
|
||||
portValue = self.net_srv.port_by_index(
|
||||
self.comboPorts.currentIndex()
|
||||
)
|
||||
except:
|
||||
portValue = self.comboPorts.currentText()
|
||||
|
||||
if portValue == "" or portValue == "0":
|
||||
return
|
||||
|
||||
# TODO: should we add a TCP/UDP port?
|
||||
portValue = portValue.replace(" ", "")
|
||||
exprs = Fw.Expr.new(
|
||||
Fw.Operator.values()[self.comboOperator.currentIndex()],
|
||||
Fw.Statements.TCP.value,
|
||||
[(Fw.Statements.DPORT.value, portValue)]
|
||||
)
|
||||
rule.Expressions.append(exprs)
|
||||
|
||||
chain.Rules.append(rule)
|
||||
|
||||
node_addr = self.comboNodes.currentText()
|
||||
node = self._nodes.get_node(node_addr)
|
||||
return node_addr, node, chain
|
||||
|
||||
def send_notification(self, node_addr, fw_config, op):
|
||||
nid, notif = self._nodes.reload_fw(node_addr, fw_config, self._notification_callback)
|
||||
self._notifications_sent[nid] = {'addr': node_addr, 'operation': op, 'notif': notif}
|
||||
|
||||
def _set_status_error(self, msg):
|
||||
self.statusLabel.show()
|
||||
self.statusLabel.setStyleSheet('color: red')
|
||||
self.statusLabel.setText(msg)
|
||||
|
||||
def _set_status_successful(self, msg):
|
||||
self.statusLabel.show()
|
||||
self.statusLabel.setStyleSheet('color: green')
|
||||
self.statusLabel.setText(msg)
|
||||
|
||||
def _set_status_message(self, msg):
|
||||
self.statusLabel.show()
|
||||
self.statusLabel.setStyleSheet('color: darkorange')
|
||||
self.statusLabel.setText(msg)
|
||||
|
||||
def _reset_status_message(self):
|
||||
self.statusLabel.setText("")
|
||||
self.statusLabel.hide()
|
||||
|
||||
def _reset_fields(self):
|
||||
self.FORM_TYPE = self.FORM_TYPE_SIMPLE
|
||||
self.setWindowTitle(QC.translate("firewall", "Firewall rule"))
|
||||
|
||||
self.cmdDelete.setVisible(False)
|
||||
self.cmdSave.setVisible(False)
|
||||
self.cmdAdd.setVisible(True)
|
||||
|
||||
self.checkEnable.setVisible(True)
|
||||
self.checkEnable.setEnabled(True)
|
||||
self.checkEnable.setChecked(True)
|
||||
self.comboOperator.setVisible(True)
|
||||
self.comboOperator.setCurrentIndex(0)
|
||||
self.frameDirection.setVisible(True)
|
||||
self.frameAction.setVisible(True)
|
||||
|
||||
self._reset_status_message()
|
||||
self._enable_buttons()
|
||||
self.tabWidget.setDisabled(False)
|
||||
self.lineDescription.setText("")
|
||||
self.comboPorts.setCurrentText("")
|
||||
self.comboDirection.setCurrentIndex(0)
|
||||
self.comboDirection.setEnabled(True)
|
||||
self.comboVerdict.setCurrentIndex(0)
|
||||
self.uuid = ""
|
||||
|
||||
def _enable_buttons(self, enable=True):
|
||||
"""Disable add/save buttons until a response is received from the daemon.
|
||||
"""
|
||||
self.cmdSave.setEnabled(enable)
|
||||
self.cmdAdd.setEnabled(enable)
|
||||
self.cmdDelete.setEnabled(enable)
|
||||
|
||||
def _disable_buttons(self, disabled=True):
|
||||
self.cmdSave.setDisabled(disabled)
|
||||
self.cmdAdd.setDisabled(disabled)
|
||||
self.cmdDelete.setDisabled(disabled)
|
||||
|
||||
def _disable_controls(self):
|
||||
self._disable_buttons()
|
||||
self.tabWidget.setDisabled(True)
|
|
@ -12,10 +12,13 @@ from opensnitch import ui_pb2
|
|||
from opensnitch.config import Config
|
||||
from opensnitch.version import version
|
||||
from opensnitch.nodes import Nodes
|
||||
from opensnitch.firewall import Firewall
|
||||
from opensnitch.dialogs.firewall import FirewallDialog
|
||||
from opensnitch.dialogs.preferences import PreferencesDialog
|
||||
from opensnitch.dialogs.ruleseditor import RulesEditorDialog
|
||||
from opensnitch.dialogs.processdetails import ProcessDetailsDialog
|
||||
from opensnitch.customwidgets.main import ColorizedDelegate, ConnectionsTableModel
|
||||
from opensnitch.customwidgets.firewalltableview import FirewallTableModel
|
||||
from opensnitch.customwidgets.generictableview import GenericTableModel
|
||||
from opensnitch.customwidgets.addresstablemodel import AddressTableModel
|
||||
from opensnitch.utils import Message, QuickHelp, AsnDB
|
||||
|
@ -70,15 +73,19 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
TAB_ADDRS = 5
|
||||
TAB_PORTS = 6
|
||||
TAB_USERS = 7
|
||||
TAB_FIREWALL = 8
|
||||
|
||||
# row of entries
|
||||
# tree's top level items
|
||||
RULES_TREE_APPS = 0
|
||||
RULES_TREE_NODES = 1
|
||||
RULES_TREE_FIREWALL = 2
|
||||
|
||||
RULES_TREE_PERMANENT = 0
|
||||
RULES_TREE_TEMPORARY = 1
|
||||
|
||||
RULES_COMBO_PERMANENT = 1
|
||||
RULES_COMBO_TEMPORARY = 2
|
||||
RULES_COMBO_FW = 3
|
||||
|
||||
RULES_TYPE_PERMANENT = 0
|
||||
RULES_TYPE_TEMPORARY = 1
|
||||
|
@ -86,6 +93,10 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
FILTER_TREE_APPS = 0
|
||||
FILTER_TREE_NODES = 3
|
||||
|
||||
FILTER_TREE_FW_NODE = 0
|
||||
FILTER_TREE_FW_TABLE = 1
|
||||
FILTER_TREE_FW_CHAIN = 2
|
||||
|
||||
# FIXME: don't translate, used only for default argument on _update_status_label
|
||||
FIREWALL_DISABLED = "Disabled"
|
||||
|
||||
|
@ -99,7 +110,8 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
TAB_PROCS: False,
|
||||
TAB_ADDRS: False,
|
||||
TAB_PORTS: False,
|
||||
TAB_USERS: False
|
||||
TAB_USERS: False,
|
||||
TAB_FIREWALL: False
|
||||
}
|
||||
# restore scrollbar position when going back from a detail view
|
||||
LAST_SCROLL_VALUE = None
|
||||
|
@ -194,6 +206,38 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
"last_order_to": 0,
|
||||
"rows_selected": False
|
||||
},
|
||||
TAB_FIREWALL: {
|
||||
"name": "firewall",
|
||||
"label": None,
|
||||
"cmd": None,
|
||||
"cmdCleanStats": None,
|
||||
"view": None,
|
||||
"filterLine": None,
|
||||
"model": None,
|
||||
"delegate": {
|
||||
Config.ACTION_DENY: RED,
|
||||
Config.ACTION_DROP: RED,
|
||||
Config.ACTION_STOP: RED,
|
||||
"DROP": RED,
|
||||
"ACCEPT": GREEN,
|
||||
Config.ACTION_REJECT: PURPLE,
|
||||
Config.ACTION_RETURN: PURPLE,
|
||||
Config.ACTION_ACCEPT: GREEN,
|
||||
Config.ACTION_JUMP: GREEN,
|
||||
Config.ACTION_MASQUERADE: GREEN,
|
||||
Config.ACTION_SNAT: GREEN,
|
||||
Config.ACTION_DNAT: GREEN,
|
||||
Config.ACTION_TPROXY: GREEN,
|
||||
"True": GREEN,
|
||||
"False": RED,
|
||||
'alignment': QtCore.Qt.AlignCenter | QtCore.Qt.AlignHCenter
|
||||
},
|
||||
"display_fields": "*",
|
||||
"header_labels": [],
|
||||
"last_order_by": "2",
|
||||
"last_order_to": 0,
|
||||
"rows_selected": False
|
||||
},
|
||||
TAB_HOSTS: {
|
||||
"name": "hosts",
|
||||
"label": None,
|
||||
|
@ -319,6 +363,8 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
|
||||
self._cfg = Config.get()
|
||||
self._nodes = Nodes.instance()
|
||||
self._fw = Firewall().instance()
|
||||
self._fw.rules.rulesUpdated.connect(self._cb_fw_rules_updated)
|
||||
|
||||
# TODO: allow to display multiples dialogs
|
||||
self._proc_details_dialog = ProcessDetailsDialog(appicon=appicon)
|
||||
|
@ -326,6 +372,10 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.prevButton.setVisible(False)
|
||||
self.nextButton.setVisible(False)
|
||||
|
||||
|
||||
self.fwTable.setVisible(False)
|
||||
self.rulesTable.setVisible(True)
|
||||
|
||||
self.daemon_connected = False
|
||||
# skip table updates if a context menu is active
|
||||
self._context_menu_active = False
|
||||
|
@ -337,6 +387,7 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self._stats = None
|
||||
self._notifications_sent = {}
|
||||
|
||||
self._fw_dialog = FirewallDialog(appicon=appicon)
|
||||
self._prefs_dialog = PreferencesDialog(appicon=appicon)
|
||||
self._rules_dialog = RulesEditorDialog(appicon=appicon)
|
||||
self._prefs_dialog.saved.connect(self._on_settings_saved)
|
||||
|
@ -347,9 +398,13 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.nodeLabel.setStyleSheet('color: green;font-size:12pt; font-weight:600;')
|
||||
self.rulesSplitter.setStretchFactor(0,0)
|
||||
self.rulesSplitter.setStretchFactor(1,2)
|
||||
self.rulesTreePanel.resizeColumnToContents(0)
|
||||
self.rulesTreePanel.resizeColumnToContents(1)
|
||||
self.rulesTreePanel.itemExpanded.connect(self._cb_rules_tree_item_expanded)
|
||||
|
||||
self.startButton.clicked.connect(self._cb_start_clicked)
|
||||
self.prefsButton.clicked.connect(self._cb_prefs_clicked)
|
||||
self.fwButton.clicked.connect(lambda: self._fw_dialog.show())
|
||||
self.saveButton.clicked.connect(self._on_save_clicked)
|
||||
self.comboAction.currentIndexChanged.connect(self._cb_combo_action_changed)
|
||||
self.limitCombo.currentIndexChanged.connect(self._cb_limit_combo_changed)
|
||||
|
@ -357,6 +412,7 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.delRuleButton.clicked.connect(self._cb_del_rule_clicked)
|
||||
self.rulesSplitter.splitterMoved.connect(self._cb_rules_splitter_moved)
|
||||
self.rulesTreePanel.itemClicked.connect(self._cb_rules_tree_item_clicked)
|
||||
self.rulesTreePanel.itemDoubleClicked.connect(self._cb_rules_tree_item_double_clicked)
|
||||
self.enableRuleCheck.clicked.connect(self._cb_enable_rule_toggled)
|
||||
self.editRuleButton.clicked.connect(self._cb_edit_rule_clicked)
|
||||
self.newRuleButton.clicked.connect(self._cb_new_rule_clicked)
|
||||
|
@ -443,6 +499,13 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
delegate=self.TABLES[self.TAB_RULES]['delegate'],
|
||||
order_by="2",
|
||||
sort_direction=self.SORT_ORDER[0])
|
||||
self.TABLES[self.TAB_FIREWALL]['view'] = self._setup_table(QtWidgets.QTableView,
|
||||
self.fwTable, "firewall",
|
||||
model=FirewallTableModel("firewall"),
|
||||
verticalScrollBar=None,
|
||||
delegate=self.TABLES[self.TAB_FIREWALL]['delegate'],
|
||||
order_by="2",
|
||||
sort_direction=self.SORT_ORDER[0])
|
||||
self.TABLES[self.TAB_HOSTS]['view'] = self._setup_table(QtWidgets.QTableView,
|
||||
self.hostsTable, "hosts",
|
||||
model=GenericTableModel("hosts", self.TABLES[self.TAB_HOSTS]['header_labels']),
|
||||
|
@ -525,18 +588,22 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
|
||||
self.TABLES[self.TAB_RULES]['view'].setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.TABLES[self.TAB_RULES]['view'].customContextMenuRequested.connect(self._cb_table_context_menu)
|
||||
for idx in range(1,8):
|
||||
self.TABLES[idx]['cmd'].hide()
|
||||
self.TABLES[idx]['cmd'].setVisible(False)
|
||||
self.TABLES[idx]['cmd'].clicked.connect(lambda: self._cb_cmd_back_clicked(idx))
|
||||
for idx in range(1,9):
|
||||
if self.TABLES[idx]['cmd'] != None:
|
||||
self.TABLES[idx]['cmd'].hide()
|
||||
self.TABLES[idx]['cmd'].setVisible(False)
|
||||
self.TABLES[idx]['cmd'].clicked.connect(lambda: self._cb_cmd_back_clicked(idx))
|
||||
if self.TABLES[idx]['cmdCleanStats'] != None:
|
||||
self.TABLES[idx]['cmdCleanStats'].clicked.connect(lambda: self._cb_clean_sql_clicked(idx))
|
||||
self.TABLES[idx]['label'].setStyleSheet('color: blue; font-size:9pt; font-weight:600;')
|
||||
self.TABLES[idx]['label'].setVisible(False)
|
||||
if self.TABLES[idx]['label'] != None:
|
||||
self.TABLES[idx]['label'].setStyleSheet('color: blue; font-size:9pt; font-weight:600;')
|
||||
self.TABLES[idx]['label'].setVisible(False)
|
||||
self.TABLES[idx]['view'].doubleClicked.connect(self._cb_table_double_clicked)
|
||||
self.TABLES[idx]['view'].selectionModel().selectionChanged.connect(self._cb_table_selection_changed)
|
||||
self.TABLES[idx]['view'].installEventFilter(self)
|
||||
|
||||
self.TABLES[self.TAB_FIREWALL]['view'].rowsReordered.connect(self._cb_fw_table_rows_reordered)
|
||||
|
||||
self._load_settings()
|
||||
|
||||
self._tables = ( \
|
||||
|
@ -563,6 +630,17 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.iconStart = QtGui.QIcon().fromTheme("media-playback-start")
|
||||
self.iconPause = QtGui.QIcon().fromTheme("media-playback-pause")
|
||||
|
||||
self.fwTreeEdit = QtWidgets.QPushButton()
|
||||
self.fwTreeEdit.setIcon(QtGui.QIcon().fromTheme("preferences-desktop"))
|
||||
self.fwTreeEdit.autoFillBackground = True
|
||||
self.fwTreeEdit.setFlat(True)
|
||||
self.fwTreeEdit.setSizePolicy(
|
||||
QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
)
|
||||
if QtGui.QIcon().hasThemeIcon("preferences-desktop") == False:
|
||||
self.fwTreeEdit.setText("+")
|
||||
self.fwTreeEdit.clicked.connect(self._cb_tree_edit_firewall_clicked)
|
||||
|
||||
if QtGui.QIcon.hasThemeIcon("document-new") == False:
|
||||
self._configure_buttons_icons()
|
||||
|
||||
|
@ -586,6 +664,7 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.nodeLabel.setText(self._address)
|
||||
self._load_settings()
|
||||
self._add_rulesTree_nodes()
|
||||
self._add_rulesTree_fw_chains()
|
||||
self.setWindowTitle(window_title)
|
||||
self._refresh_active_table()
|
||||
|
||||
|
@ -656,11 +735,12 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.comboRulesFilter.setVisible(rulesSizes[0] == 0)
|
||||
else:
|
||||
w = self.rulesSplitter.width()
|
||||
self.rulesSplitter.setSizes([int(w/4), int(w/2)])
|
||||
self.rulesSplitter.setSizes([int(w/3), int(w/2)])
|
||||
|
||||
self._restore_details_view_columns(self.eventsTable.horizontalHeader(), Config.STATS_GENERAL_COL_STATE)
|
||||
self._restore_details_view_columns(self.nodesTable.horizontalHeader(), Config.STATS_NODES_COL_STATE)
|
||||
self._restore_details_view_columns(self.rulesTable.horizontalHeader(), Config.STATS_RULES_COL_STATE)
|
||||
self._restore_details_view_columns(self.fwTable.horizontalHeader(), Config.STATS_FW_COL_STATE)
|
||||
|
||||
rulesTreeNodes_expanded = self._cfg.getBool(Config.STATS_RULES_TREE_EXPANDED_1)
|
||||
if rulesTreeNodes_expanded != None:
|
||||
|
@ -686,6 +766,8 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self._cfg.setSettings(Config.STATS_NODES_COL_STATE, nodesHeader.saveState())
|
||||
rulesHeader = self.rulesTable.horizontalHeader()
|
||||
self._cfg.setSettings(Config.STATS_RULES_COL_STATE, rulesHeader.saveState())
|
||||
fwHeader = self.fwTable.horizontalHeader()
|
||||
self._cfg.setSettings(Config.STATS_FW_COL_STATE, fwHeader.saveState())
|
||||
|
||||
rules_tree_apps = self._get_rulesTree_item(self.RULES_TREE_APPS)
|
||||
if rules_tree_apps != None:
|
||||
|
@ -702,6 +784,8 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
# https://stackoverflow.com/questions/40225270/copy-paste-multiple-items-from-qtableview-in-pyqt4
|
||||
def _copy_selected_rows(self):
|
||||
cur_idx = self.tabWidget.currentIndex()
|
||||
if self.tabWidget.currentIndex() == self.TAB_RULES and self.fwTable.isVisible():
|
||||
cur_idx = self.TAB_FIREWALL
|
||||
selection = self.TABLES[cur_idx]['view'].selectedIndexes()
|
||||
if selection:
|
||||
rows = sorted(index.row() for index in selection)
|
||||
|
@ -901,10 +985,22 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
if ret == QtWidgets.QMessageBox.Cancel:
|
||||
return False
|
||||
|
||||
for idx in selection:
|
||||
name = model.index(idx.row(), self.COL_R_NAME).data()
|
||||
node = model.index(idx.row(), self.COL_R_NODE).data()
|
||||
self._del_rule(name, node)
|
||||
if self.tabWidget.currentIndex() == self.TAB_RULES and self.fwTable.isVisible():
|
||||
do_refresh = False
|
||||
for idx in selection:
|
||||
uuid = model.index(idx.row(), 1).data()
|
||||
node = model.index(idx.row(), 2).data()
|
||||
ok, fw_config = self._fw.delete_rule(node, uuid)
|
||||
do_refresh |= ok
|
||||
|
||||
if do_refresh:
|
||||
nid, noti = self._nodes.reload_fw(node, fw_config, self._notification_callback)
|
||||
self._notifications_sent[nid] = noti
|
||||
else:
|
||||
for idx in selection:
|
||||
name = model.index(idx.row(), self.COL_R_NAME).data()
|
||||
node = model.index(idx.row(), self.COL_R_NODE).data()
|
||||
self._del_rule(name, node)
|
||||
|
||||
def _table_menu_edit(self, cur_idx, model, selection):
|
||||
|
||||
|
@ -920,6 +1016,15 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self._rules_dialog.edit_rule(records, node)
|
||||
break
|
||||
|
||||
def _cb_fw_rules_updated(self):
|
||||
self._add_rulesTree_fw_chains()
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def _cb_fw_table_rows_reordered(self, node_addr):
|
||||
node = self._nodes.get_node(node_addr)
|
||||
nid, notif = self._nodes.reload_fw(node_addr, node['firewall'], self._notification_callback)
|
||||
self._notifications_sent[nid] = {'addr': node_addr, 'notif': notif}
|
||||
|
||||
# ignore updates while the user is using the scrollbar.
|
||||
def _cb_scrollbar_pressed(self):
|
||||
self.scrollbar_active = True
|
||||
|
@ -927,6 +1032,9 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
def _cb_scrollbar_released(self):
|
||||
self.scrollbar_active = False
|
||||
|
||||
def _cb_tree_edit_firewall_clicked(self):
|
||||
self._fw_dialog.show()
|
||||
|
||||
def _cb_proc_details_clicked(self):
|
||||
table = self._tables[self.tabWidget.currentIndex()]
|
||||
nrows = table.model().rowCount()
|
||||
|
@ -1019,6 +1127,9 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
return
|
||||
elif cur_idx == StatsDialog.TAB_NODES:
|
||||
qstr = self._get_nodes_filter_query(model.query().lastQuery(), text)
|
||||
elif cur_idx == StatsDialog.TAB_RULES and self.fwTable.isVisible():
|
||||
self.TABLES[self.TAB_FIREWALL]['view'].filterByQuery(text)
|
||||
return
|
||||
elif self.IN_DETAIL_VIEW[cur_idx] == True:
|
||||
qstr = self._get_indetail_filter_query(model.query().lastQuery(), text)
|
||||
else:
|
||||
|
@ -1161,6 +1272,14 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
cur_idx = self.tabWidget.currentIndex()
|
||||
if self.IN_DETAIL_VIEW[cur_idx]:
|
||||
return
|
||||
|
||||
if cur_idx == self.TAB_RULES and self.fwTable.isVisible():
|
||||
uuid = row.model().index(row.row(), 1).data(QtCore.Qt.UserRole+1)
|
||||
addr = row.model().index(row.row(), 2).data(QtCore.Qt.UserRole+1)
|
||||
self._fw_dialog.load_rule(addr, uuid)
|
||||
return
|
||||
|
||||
|
||||
self.IN_DETAIL_VIEW[cur_idx] = True
|
||||
self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_TIME).data()
|
||||
self.LAST_SCROLL_VALUE = self.TABLES[cur_idx]['view'].vScrollBar.value()
|
||||
|
@ -1235,19 +1354,60 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self._set_rules_filter(self.RULES_TREE_APPS, self.RULES_TREE_PERMANENT)
|
||||
elif idx == self.RULES_COMBO_TEMPORARY:
|
||||
self._set_rules_filter(self.RULES_TREE_APPS, self.RULES_TREE_TEMPORARY)
|
||||
elif idx == self.RULES_COMBO_FW:
|
||||
self._set_rules_filter(-1, self.RULES_TREE_FIREWALL)
|
||||
|
||||
def _cb_rules_tree_item_expanded(self, item):
|
||||
self.rulesTreePanel.resizeColumnToContents(0)
|
||||
self.rulesTreePanel.resizeColumnToContents(1)
|
||||
|
||||
def _cb_rules_tree_item_double_clicked(self, item, col):
|
||||
# TODO: open fw chain editor
|
||||
pass
|
||||
|
||||
def _cb_rules_tree_item_clicked(self, item, col):
|
||||
"""
|
||||
Event fired when the user clicks on the left panel of the rules tab
|
||||
"""
|
||||
item_model = self.rulesTreePanel.indexFromItem(item, col)
|
||||
item_row = item_model.row()
|
||||
parent = item.parent()
|
||||
parent_row = -1
|
||||
if parent != None:
|
||||
parent_model = self.rulesTreePanel.indexFromItem(parent, col)
|
||||
parent_row = parent_model.row()
|
||||
node_addr = ""
|
||||
fw_table = ""
|
||||
|
||||
self._set_rules_filter(parent_row, item_model.row(), item.text(0))
|
||||
# FIXME: find a clever way of handling these options
|
||||
|
||||
# top level items
|
||||
if parent != None:
|
||||
parent_model = self.rulesTreePanel.indexFromItem(parent, 0)
|
||||
parent_row = parent_model.row()
|
||||
node_addr = parent_model.data()
|
||||
|
||||
# 1st level items: nodes, rules types
|
||||
if parent.parent() != None:
|
||||
parent = parent.parent()
|
||||
parent_model = self.rulesTreePanel.indexFromItem(parent, 0)
|
||||
item_row = self.FILTER_TREE_FW_TABLE
|
||||
parent_row = self.RULES_TREE_FIREWALL
|
||||
fw_table = parent_model.data()
|
||||
|
||||
# 2nd level items: chains
|
||||
if parent.parent() != None:
|
||||
parent = parent.parent()
|
||||
parent_model = self.rulesTreePanel.indexFromItem(parent.parent(), 0)
|
||||
item_row = self.FILTER_TREE_FW_CHAIN
|
||||
parent_row = self.RULES_TREE_FIREWALL
|
||||
|
||||
if node_addr == None:
|
||||
return
|
||||
|
||||
showFwTable = (parent_row == self.RULES_TREE_FIREWALL or (parent_row == -1 and item_row == self.RULES_TREE_FIREWALL))
|
||||
self.fwTable.setVisible(showFwTable)
|
||||
self.rulesTable.setVisible(not showFwTable)
|
||||
self.rulesScrollBar.setVisible(self.rulesTable.isVisible())
|
||||
|
||||
self._set_rules_filter(parent_row, item_row, item.text(0), node_addr, fw_table)
|
||||
|
||||
def _cb_rules_splitter_moved(self, pos, index):
|
||||
self.comboRulesFilter.setVisible(pos == 0)
|
||||
|
@ -1352,6 +1512,9 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.statusLabel.setStyleSheet('color: rgb(206, 92, 0); margin: 5px')
|
||||
self.startButton.setIcon(self.iconStart)
|
||||
|
||||
self._add_rulesTree_nodes()
|
||||
self._add_rulesTree_fw_chains()
|
||||
|
||||
def _get_rulesTree_item(self, index):
|
||||
try:
|
||||
return self.rulesTreePanel.topLevelItem(index)
|
||||
|
@ -1365,6 +1528,63 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
for n in self._nodes.get_nodes():
|
||||
nodesItem.addChild(QtWidgets.QTreeWidgetItem([n]))
|
||||
|
||||
def _add_rulesTree_fw_chains(self):
|
||||
expanded = list()
|
||||
selected = None
|
||||
scrollValue = self.rulesTreePanel.verticalScrollBar().value()
|
||||
fwItem = self.rulesTreePanel.topLevelItem(self.RULES_TREE_FIREWALL)
|
||||
it = QtWidgets.QTreeWidgetItemIterator(fwItem)
|
||||
while it.value():
|
||||
x = it.value()
|
||||
if x.isExpanded():
|
||||
expanded.append(x)
|
||||
if x.isSelected():
|
||||
selected = x
|
||||
it += 1
|
||||
|
||||
self.rulesTreePanel.setAnimated(False)
|
||||
fwItem.takeChildren()
|
||||
self.rulesTreePanel.setItemWidget(fwItem, 1, self.fwTreeEdit)
|
||||
chains = self._fw.get_chains()
|
||||
for addr in chains:
|
||||
# add nodes
|
||||
nodeRoot = QtWidgets.QTreeWidgetItem(["{0}".format(addr)])
|
||||
fwItem.addChild(nodeRoot)
|
||||
for nodeChains in chains[addr]:
|
||||
# exclude legacy system rules
|
||||
if len(nodeChains) == 0:
|
||||
continue
|
||||
for cc in nodeChains:
|
||||
# add tables
|
||||
tableName = "{0}-{1}".format(cc.Table, cc.Family)
|
||||
nodeTable = QtWidgets.QTreeWidgetItem([tableName])
|
||||
|
||||
chainName = "{0}-{1}".format(cc.Name, cc.Hook)
|
||||
nodeChain = QtWidgets.QTreeWidgetItem([chainName, cc.Policy])
|
||||
|
||||
items = self.rulesTreePanel.findItems(tableName, QtCore.Qt.MatchRecursive, 0)
|
||||
if len(items) == 0:
|
||||
# add table
|
||||
nodeTable.addChild(nodeChain)
|
||||
nodeRoot.addChild(nodeTable)
|
||||
else:
|
||||
# add chains
|
||||
node = items[0]
|
||||
node.addChild(nodeChain)
|
||||
|
||||
for item in expanded:
|
||||
items = self.rulesTreePanel.findItems(item.text(0), QtCore.Qt.MatchRecursive)
|
||||
for it in items:
|
||||
it.setExpanded(True)
|
||||
if selected != None and selected.text(0) == it.text(0):
|
||||
it.setSelected(True)
|
||||
|
||||
self.rulesTreePanel.verticalScrollBar().setValue(scrollValue)
|
||||
self.rulesTreePanel.setAnimated(True)
|
||||
self.rulesTreePanel.resizeColumnToContents(0)
|
||||
self.rulesTreePanel.resizeColumnToContents(1)
|
||||
expanded = None
|
||||
|
||||
def _clear_rows_selection(self):
|
||||
cur_idx = self.tabWidget.currentIndex()
|
||||
self.TABLES[cur_idx]['view'].selectionModel().reset()
|
||||
|
@ -1418,6 +1638,8 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.setQuery(model, lastQuery)
|
||||
|
||||
def _get_active_table(self):
|
||||
if self.tabWidget.currentIndex() == self.TAB_RULES and self.fwTable.isVisible():
|
||||
return self.TABLES[self.TAB_FIREWALL]['view']
|
||||
return self.TABLES[self.tabWidget.currentIndex()]['view']
|
||||
|
||||
def _set_active_widgets(self, state, label_txt=""):
|
||||
|
@ -1489,14 +1711,13 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self._set_rules_filter(parent.row(), item_m.row(), item_m.data())
|
||||
|
||||
def _set_rules_tab_active(self, row, cur_idx, name_idx, node_idx):
|
||||
data = row.data()
|
||||
self._restore_rules_tab_widgets(False)
|
||||
|
||||
self.comboRulesFilter.setVisible(False)
|
||||
|
||||
r_name = row.model().index(row.row(), name_idx).data()
|
||||
node = row.model().index(row.row(), node_idx).data()
|
||||
self.nodeRuleLabel.setText(node)
|
||||
|
||||
self.tabWidget.setCurrentIndex(cur_idx)
|
||||
|
||||
return r_name, node
|
||||
|
@ -1593,13 +1814,18 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
|
||||
return qstr
|
||||
|
||||
def _set_rules_filter(self, parent_row=-1, item_row=0, what=""):
|
||||
def _set_rules_filter(self, parent_row=-1, item_row=0, what="", what1="", what2=""):
|
||||
section = self.FILTER_TREE_APPS
|
||||
|
||||
if parent_row == -1:
|
||||
self.fwTable.setVisible(item_row == self.RULES_TREE_FIREWALL)
|
||||
self.rulesTable.setVisible(item_row != self.RULES_TREE_FIREWALL)
|
||||
if item_row == self.RULES_TREE_NODES:
|
||||
section=self.FILTER_TREE_NODES
|
||||
what=""
|
||||
elif item_row == self.RULES_TREE_FIREWALL:
|
||||
self.TABLES[self.TAB_FIREWALL]['view'].model().filterAll()
|
||||
return
|
||||
else:
|
||||
section=self.FILTER_TREE_APPS
|
||||
what=""
|
||||
|
@ -1615,6 +1841,18 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
elif parent_row == self.RULES_TREE_NODES:
|
||||
section=self.FILTER_TREE_NODES
|
||||
|
||||
elif parent_row == self.RULES_TREE_FIREWALL:
|
||||
if item_row == self.FILTER_TREE_FW_NODE:
|
||||
self.TABLES[self.TAB_FIREWALL]['view'].filterByNode(what)
|
||||
elif item_row == self.FILTER_TREE_FW_TABLE:
|
||||
parm = what.split("-")
|
||||
self.TABLES[self.TAB_FIREWALL]['view'].filterByTable(what1, parm[0], parm[1])
|
||||
elif item_row == self.FILTER_TREE_FW_CHAIN: # + table
|
||||
parm = what.split("-")
|
||||
tbl = what1.split("-")
|
||||
self.TABLES[self.TAB_FIREWALL]['view'].filterByChain(what2, tbl[0], tbl[1], parm[0], parm[1])
|
||||
return
|
||||
|
||||
if section == self.FILTER_TREE_APPS:
|
||||
if what == self.RULES_TYPE_TEMPORARY:
|
||||
what = "WHERE r.duration != '%s'" % Config.DURATION_ALWAYS
|
||||
|
@ -1934,8 +2172,8 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
|
||||
if verticalScrollBar != None:
|
||||
tableWidget.setVerticalScrollBar(verticalScrollBar)
|
||||
tableWidget.vScrollBar.sliderPressed.connect(self._cb_scrollbar_pressed)
|
||||
tableWidget.vScrollBar.sliderReleased.connect(self._cb_scrollbar_released)
|
||||
tableWidget.verticalScrollBar().sliderPressed.connect(self._cb_scrollbar_pressed)
|
||||
tableWidget.verticalScrollBar().sliderReleased.connect(self._cb_scrollbar_released)
|
||||
|
||||
self.setQuery(model, "SELECT " + fields + " FROM " + table_name + group_by + " ORDER BY " + order_by + " " + sort_direction + limit)
|
||||
tableWidget.setModel(model)
|
||||
|
|
180
ui/opensnitch/firewall/__init__.py
Normal file
180
ui/opensnitch/firewall/__init__.py
Normal file
|
@ -0,0 +1,180 @@
|
|||
from PyQt5.QtCore import QObject, QCoreApplication as QC
|
||||
from google.protobuf import json_format
|
||||
from opensnitch import ui_pb2
|
||||
|
||||
from opensnitch.nodes import Nodes
|
||||
from .enums import *
|
||||
from .rules import *
|
||||
from .chains import *
|
||||
from .utils import Utils
|
||||
from .exprs import *
|
||||
from .profiles import *
|
||||
|
||||
class Firewall(QObject):
|
||||
__instance = None
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
if Firewall.__instance == None:
|
||||
Firewall.__instance = Firewall()
|
||||
return Firewall.__instance
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QObject.__init__(self)
|
||||
self._nodes = Nodes.instance()
|
||||
self.rules = Rules(self._nodes)
|
||||
self.chains = Chains(self._nodes)
|
||||
|
||||
def switch_rules(self, key, old_pos, new_pos):
|
||||
pass
|
||||
|
||||
def add_rule(self, addr, rule):
|
||||
return self.rules.add(addr, rule)
|
||||
|
||||
def insert_rule(self, addr, rule, position=0):
|
||||
return self.rules.insert(addr, rule, position)
|
||||
|
||||
def update_rule(self, addr, uuid, rule):
|
||||
return self.rules.update(addr, uuid, rule)
|
||||
|
||||
def delete_rule(self, addr, uuid):
|
||||
return self.rules.delete(addr, uuid)
|
||||
|
||||
def get_rule_by_uuid(self, uuid):
|
||||
if uuid == "":
|
||||
return None, None
|
||||
for addr in self._nodes.get_nodes():
|
||||
node = self._nodes.get_node(addr)
|
||||
if not 'fwrules' in node:
|
||||
continue
|
||||
r = node['fwrules'].get(uuid)
|
||||
if r != None:
|
||||
return addr, r
|
||||
|
||||
return None, None
|
||||
|
||||
def filter_rules(self, nail):
|
||||
"""
|
||||
"""
|
||||
chains = []
|
||||
for addr in self._nodes.get_nodes():
|
||||
node = self._nodes.get_node(addr)
|
||||
if not 'firewall' in node:
|
||||
return chains
|
||||
for n in node['firewall'].SystemRules:
|
||||
for c in n.Chains:
|
||||
for r in c.Rules:
|
||||
if nail in c.Family or \
|
||||
nail in c.Hook or \
|
||||
nail in r.Description or \
|
||||
nail in r.Target or \
|
||||
nail in r.TargetParameters:
|
||||
# TODO: filter expressions
|
||||
#nail in r.Expressions:
|
||||
chains.append(Rules.to_array(addr, c, r))
|
||||
|
||||
return chains
|
||||
|
||||
def apply_profile(self, node_addr, json_profile):
|
||||
"""
|
||||
Apply a profile to the firewall configuration.
|
||||
|
||||
Given a chain (table+family+type+hook), apply its policy, and any rules
|
||||
defined.
|
||||
"""
|
||||
try:
|
||||
holder = ui_pb2.FwChain()
|
||||
profile = json_format.Parse(json_profile, holder)
|
||||
|
||||
fwcfg = self._nodes.get_node(node_addr)['firewall']
|
||||
for sdx, n in enumerate(fwcfg.SystemRules):
|
||||
for cdx, c in enumerate(n.Chains):
|
||||
|
||||
if c.Hook.lower() == profile.Hook and \
|
||||
c.Type.lower() == profile.Type and \
|
||||
c.Family.lower() == profile.Family and \
|
||||
c.Table.lower() == profile.Table:
|
||||
|
||||
fwcfg.SystemRules[sdx].Chains[cdx].Policy = profile.Policy
|
||||
for r in profile.Rules:
|
||||
temp_c = ui_pb2.FwChain()
|
||||
temp_c.CopyFrom(c)
|
||||
del temp_c.Rules[:]
|
||||
temp_c.Rules.extend([r])
|
||||
|
||||
if self.rules.is_duplicated(node_addr, temp_c):
|
||||
continue
|
||||
fwcfg.SystemRules[sdx].Chains[cdx].Rules.extend([r])
|
||||
|
||||
self.rules.rulesUpdated.emit()
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
print("firewall: error applying profile:", e)
|
||||
return False, "{0}".format(e)
|
||||
|
||||
return False, QC.translate("firewall", "profile not applied")
|
||||
|
||||
def delete_profile(self, node_addr, json_profile):
|
||||
try:
|
||||
holder = ui_pb2.FwChain()
|
||||
profile = json_format.Parse(json_profile, holder)
|
||||
|
||||
fwcfg = self._nodes.get_node(node_addr)['firewall']
|
||||
for sdx, n in enumerate(fwcfg.SystemRules):
|
||||
for cdx, c in enumerate(n.Chains):
|
||||
if c.Hook.lower() == profile.Hook and \
|
||||
c.Type.lower() == profile.Type and \
|
||||
c.Family.lower() == profile.Family and \
|
||||
c.Table.lower() == profile.Table:
|
||||
|
||||
if profile.Policy == ProfileDropInput.value:
|
||||
profile.Policy = ProfileAcceptInput.value
|
||||
|
||||
for rdx, r in enumerate(c.Rules):
|
||||
for pr in profile.Rules:
|
||||
if r.UUID == pr.UUID:
|
||||
print("delete_profile, rule:", r.UUID, r.Description)
|
||||
del fwcfg.SystemRules[sdx].Chains[cdx].Rules[rdx]
|
||||
|
||||
except Exception as e:
|
||||
print("firewall: error deleting profile:", e)
|
||||
|
||||
def swap_rules(self, view, addr, uuid, old_pos, new_pos):
|
||||
return self.rules.swap(view, addr, uuid, old_pos, new_pos)
|
||||
|
||||
def filter_by_table(self, addr, table, family):
|
||||
"""get rules by table"""
|
||||
chains = []
|
||||
node = self._nodes.get_node(addr)
|
||||
if not 'firewall' in node:
|
||||
return chains
|
||||
for n in node['firewall'].SystemRules:
|
||||
for c in n.Chains:
|
||||
for r in c.Rules:
|
||||
if c.Table == table and c.Family == family:
|
||||
chains.append(Rules.to_array(addr, c, r))
|
||||
|
||||
return chains
|
||||
|
||||
def filter_by_chain(self, addr, table, family, chain, hook):
|
||||
"""get rules by chain"""
|
||||
chains = []
|
||||
node = self._nodes.get_node(addr)
|
||||
if not 'firewall' in node:
|
||||
return chains
|
||||
for n in node['firewall'].SystemRules:
|
||||
for c in n.Chains:
|
||||
for r in c.Rules:
|
||||
if c.Table == table and c.Family == family and c.Name == chain and c.Hook == hook:
|
||||
chains.append(Rules.to_array(addr, c, r))
|
||||
|
||||
return chains
|
||||
|
||||
def get_node_rules(self, addr):
|
||||
return self.rules.get_by_node(addr)
|
||||
|
||||
def get_chains(self):
|
||||
return self.chains.get()
|
||||
|
||||
def get_rules(self):
|
||||
return self.rules.get()
|
212
ui/opensnitch/firewall/chains.py
Normal file
212
ui/opensnitch/firewall/chains.py
Normal file
|
@ -0,0 +1,212 @@
|
|||
from opensnitch import ui_pb2
|
||||
from .enums import *
|
||||
|
||||
class Chains():
|
||||
|
||||
def __init__(self, nodes):
|
||||
self._nodes = nodes
|
||||
|
||||
def get(self):
|
||||
chains = {}
|
||||
for node in self._nodes.get_nodes():
|
||||
chains[node] = self.get_node_chains(node)
|
||||
return chains
|
||||
|
||||
def get_node_chains(self, addr):
|
||||
node = self._nodes.get_node(addr)
|
||||
if node == None:
|
||||
return rules
|
||||
if not 'firewall' in node:
|
||||
return rules
|
||||
|
||||
chains = []
|
||||
for c in node['firewall'].SystemRules:
|
||||
# Chains node does not exist on <= v1.5.x
|
||||
try:
|
||||
chains.append(c.Chains)
|
||||
except Exception:
|
||||
pass
|
||||
return chains
|
||||
|
||||
def get_node_chains(self, addr):
|
||||
node = self._nodes.get_node(addr)
|
||||
if node == None:
|
||||
return rules
|
||||
if not 'firewall' in node:
|
||||
return rules
|
||||
|
||||
chains = []
|
||||
for c in node['firewall'].SystemRules:
|
||||
# Chains node does not exist on <= v1.5.x
|
||||
try:
|
||||
chains.append(c.Chains)
|
||||
except Exception:
|
||||
pass
|
||||
return chains
|
||||
|
||||
|
||||
|
||||
def get_policy(self, node_addr=None, hook=Hooks.INPUT.value, _type=ChainType.FILTER.value, family=Family.INET.value):
|
||||
fwcfg = self._nodes.get_node(node_addr)['firewall']
|
||||
for sdx, n in enumerate(fwcfg.SystemRules):
|
||||
for cdx, c in enumerate(n.Chains):
|
||||
if c.Hook.lower() == hook and c.Type.lower() == _type and c.Family.lower() == family:
|
||||
return c.Policy
|
||||
|
||||
return None
|
||||
|
||||
def set_policy(self, node_addr, hook=Hooks.INPUT.value, _type=ChainType.FILTER.value, family=Family.INET.value, policy=Policy.DROP):
|
||||
fwcfg = self._nodes.get_node(node_addr)['firewall']
|
||||
for sdx, n in enumerate(fwcfg.SystemRules):
|
||||
for cdx, c in enumerate(n.Chains):
|
||||
# XXX: support only "inet" family (ipv4/ipv6)? or allow to
|
||||
# specify ipv4 OR/AND ipv6? some systems have ipv6 disabled
|
||||
if c.Hook.lower() == hook and c.Type.lower() == _type and c.Family.lower() == family:
|
||||
fwcfg.SystemRules[sdx].Chains[cdx].Policy = policy
|
||||
|
||||
if wantedHook == Fw.Hooks.INPUT.value and wantedPolicy == Fw.Policy.DROP.value:
|
||||
fwcfg.SystemRules[sdx].Chains[cdx].Rules.append(rule.Rules[0])
|
||||
self._nodes.add_fw_config(node_addr, fwcfg)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@staticmethod
|
||||
def new(
|
||||
name="",
|
||||
table=Table.FILTER.value,
|
||||
family=Family.INET.value,
|
||||
ctype="",
|
||||
hook=Hooks.INPUT.value
|
||||
):
|
||||
chain = ui_pb2.FwChain()
|
||||
chain.Name = name
|
||||
chain.Table = table
|
||||
chain.Family = family
|
||||
chain.Type = ctype
|
||||
chain.Hook = hook
|
||||
|
||||
return chain
|
||||
|
||||
# man nft
|
||||
# Table 6. Standard priority names, family and hook compatibility matrix
|
||||
# Name │ Value │ Families │ Hooks
|
||||
# raw │ -300 │ ip, ip6, inet │ all
|
||||
# mangle │ -150 │ ip, ip6, inet │ all
|
||||
# dstnat │ -100 │ ip, ip6, inet │ prerouting
|
||||
# filter │ 0 │ ip, ip6, inet, arp, netdev │ all
|
||||
# security │ 50 │ ip, ip6, inet │ all
|
||||
# srcnat │ 100 │ ip, ip6, inet │ postrouting
|
||||
#
|
||||
|
||||
class ChainFilter(Chains):
|
||||
"""
|
||||
ChainFilter returns a new chain of type filter.
|
||||
|
||||
The name of the chain is the one listed with: nft list table inet filter.
|
||||
It corresponds with the hook name, but can be a random name.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def input(family=Family.INET.value):
|
||||
chain = ui_pb2.FwChain()
|
||||
chain.Name = Hooks.INPUT.value
|
||||
chain.Table = Table.FILTER.value
|
||||
chain.Family = family
|
||||
chain.Type = ChainType.FILTER.value
|
||||
chain.Hook = Hooks.INPUT.value
|
||||
|
||||
return chain
|
||||
|
||||
@staticmethod
|
||||
def output(family=Family.INET.value):
|
||||
chain = ui_pb2.FwChain()
|
||||
chain.Name = Hooks.OUTPUT.value
|
||||
chain.Table = Table.FILTER.value
|
||||
chain.Family = family
|
||||
chain.Type = ChainType.FILTER.value
|
||||
chain.Hook = Hooks.OUTPUT.value
|
||||
|
||||
return chain
|
||||
|
||||
@staticmethod
|
||||
def forward(family=Family.INET.value):
|
||||
chain = ui_pb2.FwChain()
|
||||
chain.Name = Hooks.FORWARD.value
|
||||
chain.Table = Table.FILTER.value
|
||||
chain.Family = family
|
||||
chain.Type = ChainType.FILTER.value
|
||||
chain.Hook = Hooks.FORWARD.value
|
||||
|
||||
return chain
|
||||
|
||||
|
||||
|
||||
class ChainMangle(Chains):
|
||||
"""
|
||||
ChainMangle returns a new chain of type mangle.
|
||||
|
||||
The name of the chain is the one listed with: nft list table inet mangle.
|
||||
It corresponds with the hook name, but can be a random name.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def output(family=Family.INET.value):
|
||||
chain = ui_pb2.FwChain()
|
||||
chain.Name = Hooks.OUTPUT.value
|
||||
chain.Table = Table.MANGLE.value
|
||||
|
||||
chain.Family = family
|
||||
chain.Type = ChainType.MANGLE.value
|
||||
chain.Hook = Hooks.OUTPUT.value
|
||||
|
||||
return chain
|
||||
|
||||
@staticmethod
|
||||
def input(family=Family.INET.value):
|
||||
chain = ui_pb2.FwChain(family=Family.INET.value)
|
||||
chain.Name = Hooks.INPUT.value
|
||||
chain.Table = Table.MANGLE.value
|
||||
|
||||
chain.Family = family
|
||||
chain.Type = ChainType.MANGLE.value
|
||||
chain.Hook = Hooks.INPUT.value
|
||||
|
||||
return chain
|
||||
|
||||
@staticmethod
|
||||
def forward(family=Family.INET.value):
|
||||
chain = ui_pb2.FwChain()
|
||||
chain.Name = Hooks.FORWARD.value
|
||||
chain.Table = Table.MANGLE.value
|
||||
|
||||
chain.Family = family
|
||||
chain.Type = ChainType.MANGLE.value
|
||||
chain.Hook = Hooks.PREROUTING.value
|
||||
|
||||
return chain
|
||||
|
||||
|
||||
@staticmethod
|
||||
def prerouting(family=Family.INET.value):
|
||||
chain = ui_pb2.FwChain()
|
||||
chain.Name = Hooks.PREROUTING.value
|
||||
chain.Table = Table.MANGLE.value
|
||||
|
||||
chain.Family = family
|
||||
chain.Type = ChainType.MANGLE.value
|
||||
chain.Hook = Hooks.PREROUTING.value
|
||||
|
||||
return chain
|
||||
|
||||
@staticmethod
|
||||
def postrouting(family=Family.INET.value):
|
||||
chain = ui_pb2.FwChain()
|
||||
chain.Name = Hooks.POSTROUTING.value
|
||||
chain.Table = Table.MANGLE.value
|
||||
|
||||
chain.Family = family
|
||||
chain.Type = ChainType.MANGLE.value
|
||||
chain.Hook = Hooks.POSTROUTING.value
|
||||
|
||||
return chain
|
64
ui/opensnitch/firewall/enums.py
Normal file
64
ui/opensnitch/firewall/enums.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
from opensnitch.utils import Enums
|
||||
from opensnitch.config import Config
|
||||
|
||||
class Verdicts(Enums):
|
||||
EMPTY = ""
|
||||
ACCEPT = Config.ACTION_ACCEPT
|
||||
DROP = Config.ACTION_DROP
|
||||
REJECT = Config.ACTION_REJECT
|
||||
RETURN = Config.ACTION_RETURN
|
||||
STOP = Config.ACTION_STOP
|
||||
|
||||
class Policy(Enums):
|
||||
ACCEPT = "accept"
|
||||
DROP = "drop"
|
||||
|
||||
class Table(Enums):
|
||||
FILTER = "filter"
|
||||
MANGLE = "mangle"
|
||||
NAT = "nat"
|
||||
|
||||
class Hooks(Enums):
|
||||
INPUT ="input"
|
||||
OUTPUT ="output"
|
||||
FORWARD = "forward"
|
||||
PREROUTING = "prerouting"
|
||||
POSTROUTING = "postrouting"
|
||||
|
||||
class Family(Enums):
|
||||
INET = "inet"
|
||||
IPv4 = "ip"
|
||||
IPv6 = "ip6"
|
||||
|
||||
class ChainType(Enums):
|
||||
FILTER = "filter"
|
||||
MANGLE = "mangle"
|
||||
ROUTE = "route"
|
||||
SNAT = "snat"
|
||||
DNAT = "dnat"
|
||||
|
||||
class Operator(Enums):
|
||||
EQUAL = "=="
|
||||
NOT_EQUAL = "!="
|
||||
GT_THAN = ">="
|
||||
GT = ">"
|
||||
LT_THAN = "<="
|
||||
LT = "<"
|
||||
|
||||
class Statements(Enums):
|
||||
"""Enum of known (allowed) statements:
|
||||
[tcp,udp,ip] ...
|
||||
"""
|
||||
|
||||
TCP = "tcp"
|
||||
UDP = "udp"
|
||||
UDPLITE = "udplite"
|
||||
SCTP = "sctp"
|
||||
DCCP = "dccp"
|
||||
ICMP = "icmp"
|
||||
SPORT = "sport"
|
||||
DPORT = "dport"
|
||||
|
||||
IP = "ip"
|
||||
IIFNAME = "iifname"
|
||||
OIFNAME = "oifname"
|
41
ui/opensnitch/firewall/exprs.py
Normal file
41
ui/opensnitch/firewall/exprs.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
from opensnitch import ui_pb2
|
||||
from .enums import *
|
||||
|
||||
class Expr():
|
||||
"""
|
||||
Expr returns a new nftables expression that defines a match or an action:
|
||||
tcp dport 22, udp sport 53
|
||||
log prefix "xxx"
|
||||
|
||||
Attributes:
|
||||
op (string): operator (==, !=, ...).
|
||||
what (string): name of the statement (tcp, udp, ip, ...)
|
||||
value (tuple): array of values (dport -> 22, etc).
|
||||
"""
|
||||
@staticmethod
|
||||
def new(op, what, values):
|
||||
expr = ui_pb2.Expressions()
|
||||
expr.Statement.Op = op
|
||||
expr.Statement.Name = what
|
||||
|
||||
for val in values:
|
||||
exprValues = ui_pb2.StatementValues()
|
||||
exprValues.Key = val[0]
|
||||
exprValues.Value = val[1]
|
||||
expr.Statement.Values.append(exprValues)
|
||||
|
||||
return expr
|
||||
|
||||
class ExprCt(Enums):
|
||||
STATE = "state"
|
||||
NEW = "new"
|
||||
ESTABLISHED = "established"
|
||||
RELATED = "related"
|
||||
|
||||
class ExprIface(Enums):
|
||||
IIFNAME = "iifname"
|
||||
OIFNAME = "oifname"
|
||||
|
||||
class ExprLog(Enums):
|
||||
LOG = "log"
|
157
ui/opensnitch/firewall/profiles.py
Normal file
157
ui/opensnitch/firewall/profiles.py
Normal file
|
@ -0,0 +1,157 @@
|
|||
|
||||
import glob
|
||||
import json
|
||||
import os.path
|
||||
|
||||
|
||||
class Profiles():
|
||||
|
||||
@staticmethod
|
||||
def load_predefined_profiles():
|
||||
profiles = glob.glob("/etc/opensnitchd/system-fw.d/profiles/*.profile")
|
||||
p = []
|
||||
for pr_path in profiles:
|
||||
with open(pr_path) as f:
|
||||
p.append({os.path.basename(pr_path): json.load(f)})
|
||||
|
||||
return p
|
||||
|
||||
|
||||
class ProfileAcceptOutput():
|
||||
value = {
|
||||
"Name": "accept-mangle-output",
|
||||
"Table": "mangle",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "mangle",
|
||||
"Hook": "output",
|
||||
"Policy": "accept",
|
||||
"Rules": [
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class ProfileDropOutput():
|
||||
value = {
|
||||
"Name": "drop-mangle-output",
|
||||
"Table": "mangle",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "mangle",
|
||||
"Hook": "output",
|
||||
"Policy": "drop",
|
||||
"Rules": [
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class ProfileAcceptForward():
|
||||
value = {
|
||||
"Name": "accept-mangle-forward",
|
||||
"Table": "mangle",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "mangle",
|
||||
"Hook": "forward",
|
||||
"Policy": "accept",
|
||||
"Rules": [
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class ProfileDropForward():
|
||||
value = {
|
||||
"Name": "drop-mangle-forward",
|
||||
"Table": "mangle",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "mangle",
|
||||
"Hook": "forward",
|
||||
"Policy": "drop",
|
||||
"Rules": [
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class ProfileAcceptInput():
|
||||
value = {
|
||||
"Name": "accept-filter-input",
|
||||
"Table": "filter",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "filter",
|
||||
"Hook": "input",
|
||||
"Policy": "accept",
|
||||
"Rules": [
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class ProfileDropInput():
|
||||
"""
|
||||
Set input filter table policy to DROP and add the needed rules to allow
|
||||
outbound connections.
|
||||
"""
|
||||
|
||||
# TODO: delete dropInput profile's rules
|
||||
value = {
|
||||
"Name": "drop-filter-input",
|
||||
"Table": "filter",
|
||||
"Family": "inet",
|
||||
"Priority": "",
|
||||
"Type": "filter",
|
||||
"Hook": "input",
|
||||
"Policy": "drop",
|
||||
"Rules": [
|
||||
{
|
||||
"Table": "",
|
||||
"Chain": "",
|
||||
"UUID": "profile-drop-inbound-2d7e6fe4-c21d-11ec-99a6-3c970e298b0c",
|
||||
"Enabled": True,
|
||||
"Position": "0",
|
||||
"Description": "[profile-drop-inbound] allow localhost connections",
|
||||
"Parameters": "",
|
||||
"Expressions": [
|
||||
{
|
||||
"Statement": {
|
||||
"Op": "",
|
||||
"Name": "iifname",
|
||||
"Values": [
|
||||
{
|
||||
"Key": "lo",
|
||||
"Value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Target": "accept",
|
||||
"TargetParameters": ""
|
||||
},
|
||||
{
|
||||
"Enabled": True,
|
||||
"Description": "[profile-drop-inbound] allow established,related connections",
|
||||
"UUID": "profile-drop-inbound-e1fc1a1c-c21c-11ec-9a2a-3c970e298b0c",
|
||||
"Expressions": [
|
||||
{
|
||||
"Statement": {
|
||||
"Op": "",
|
||||
"Name": "ct",
|
||||
"Values": [
|
||||
{
|
||||
"Key": "state",
|
||||
"Value": "related"
|
||||
},
|
||||
{
|
||||
"Key": "state",
|
||||
"Value": "established"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Target": "accept",
|
||||
"TargetParameters": ""
|
||||
}
|
||||
]
|
||||
}
|
275
ui/opensnitch/firewall/rules.py
Normal file
275
ui/opensnitch/firewall/rules.py
Normal file
|
@ -0,0 +1,275 @@
|
|||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
from PyQt5.QtCore import QCoreApplication as QC
|
||||
import uuid
|
||||
from opensnitch import ui_pb2
|
||||
|
||||
class Rules(QObject):
|
||||
rulesUpdated = pyqtSignal()
|
||||
|
||||
def __init__(self, nodes):
|
||||
QObject.__init__(self)
|
||||
self._nodes = nodes
|
||||
self.rulesUpdated.connect(self._cb_rules_updated)
|
||||
|
||||
def _cb_rules_updated(self):
|
||||
pass
|
||||
|
||||
def add(self, addr, rule):
|
||||
"""Add a new rule to the corresponding table on the given node
|
||||
"""
|
||||
node = self._nodes.get_node(addr)
|
||||
if not 'firewall' in node:
|
||||
return False, QC.translate("firewall", "rule not found by its ID.")
|
||||
if self.is_duplicated(addr, rule):
|
||||
return False, QC.translate("firewall", "duplicated.")
|
||||
|
||||
for sdx, n in enumerate(node['firewall'].SystemRules):
|
||||
for cdx, c in enumerate(n.Chains):
|
||||
if c.Name == rule.Name and \
|
||||
c.Hook == rule.Hook and \
|
||||
c.Table == rule.Table and \
|
||||
c.Family == rule.Family and \
|
||||
c.Type == rule.Type:
|
||||
node['firewall'].SystemRules[sdx].Chains[cdx].Rules.append(rule.Rules[0])
|
||||
node['fwrules'][rule.Rules[0].UUID] = rule
|
||||
self._nodes.add_fw_config(addr, node['firewall'])
|
||||
self._nodes.add_fw_rules(addr, node['fwrules'])
|
||||
|
||||
self.rulesUpdated.emit()
|
||||
return True
|
||||
|
||||
return False, QC.translate("firewall", "firewall table/chain not properly configured.")
|
||||
|
||||
def insert(self, addr, rule, position=0):
|
||||
"""Insert a new rule to the corresponding table on the given node
|
||||
"""
|
||||
node = self._nodes.get_node(addr)
|
||||
if not 'firewall' in node:
|
||||
return False, QC.translate("firewall", "this node doesn't have a firewall configuration, review it.")
|
||||
if self.is_duplicated(addr, rule):
|
||||
return False, QC.translate("firewall", "duplicated")
|
||||
|
||||
for sdx, n in enumerate(node['firewall'].SystemRules):
|
||||
for cdx, c in enumerate(n.Chains):
|
||||
if c.Name == rule.Name and \
|
||||
c.Hook == rule.Hook and \
|
||||
c.Table == rule.Table and \
|
||||
c.Family == rule.Family and \
|
||||
c.Type == rule.Type:
|
||||
node['firewall'].SystemRules[sdx].Chains[cdx].Rules.insert(int(position), rule.Rules[0])
|
||||
node['fwrules'][rule.Rules[0].UUID] = rule
|
||||
self._nodes.add_fw_config(addr, node['firewall'])
|
||||
self._nodes.add_fw_rules(addr, node['fwrules'])
|
||||
|
||||
self.rulesUpdated.emit()
|
||||
return True, ""
|
||||
|
||||
return False, QC.translate("firewall", "firewall table/chain not properly configured.")
|
||||
|
||||
|
||||
def update(self, addr, uuid, rule):
|
||||
node = self._nodes.get_node(addr)
|
||||
if not 'firewall' in node:
|
||||
return False, QC.translate("firewall", "this node doesn't have a firewall configuration, review it.")
|
||||
for sdx, n in enumerate(node['firewall'].SystemRules):
|
||||
for cdx, c in enumerate(n.Chains):
|
||||
for rdx, r in enumerate(c.Rules):
|
||||
if r.UUID == uuid:
|
||||
c.Rules[rdx].CopyFrom(rule.Rules[0])
|
||||
node['firewall'].SystemRules[sdx].Chains[cdx].Rules[rdx].CopyFrom(rule.Rules[0])
|
||||
self._nodes.add_fw_config(addr, node['firewall'])
|
||||
node['fwrules'][uuid] = rule
|
||||
self._nodes.add_fw_rules(addr, node['fwrules'])
|
||||
|
||||
self.rulesUpdated.emit()
|
||||
return True, ""
|
||||
|
||||
return False, QC.translate("firewall", "rule not found by its ID.")
|
||||
|
||||
def get(self):
|
||||
rules = []
|
||||
for node in self._nodes.get_nodes():
|
||||
node_rules = self.get_by_node(node)
|
||||
rules += node_rules
|
||||
|
||||
return rules
|
||||
|
||||
def delete(self, addr, uuid):
|
||||
node = self._nodes.get_node(addr)
|
||||
if not 'firewall' in node:
|
||||
return False
|
||||
for sdx, n in enumerate(node['firewall'].SystemRules):
|
||||
for cdx, c in enumerate(n.Chains):
|
||||
for idx, r in enumerate(c.Rules):
|
||||
if r.UUID == uuid:
|
||||
del node['firewall'].SystemRules[sdx].Chains[cdx].Rules[idx]
|
||||
self._nodes.add_fw_config(addr, node['firewall'])
|
||||
if uuid in node['fwrules']:
|
||||
del node['fwrules'][uuid]
|
||||
self._nodes.add_fw_rules(addr, node['fwrules'])
|
||||
else:
|
||||
# raise Error("rules doesn't have UUID field")
|
||||
print("[firewall] delete() error:", uuid)
|
||||
return False, None
|
||||
|
||||
self.rulesUpdated.emit()
|
||||
return True, node['firewall']
|
||||
|
||||
return False, None
|
||||
|
||||
def get_by_node(self, addr):
|
||||
rules = []
|
||||
node = self._nodes.get_node(addr)
|
||||
if node == None:
|
||||
return rules
|
||||
if not 'firewall' in node:
|
||||
return rules
|
||||
for u in node['firewall'].SystemRules:
|
||||
for c in u.Chains:
|
||||
for r in c.Rules:
|
||||
rules.append(Rules.to_array(addr, c, r))
|
||||
return rules
|
||||
|
||||
|
||||
def swap(self, view, addr, uuid, old_pos, new_pos):
|
||||
"""
|
||||
swap changes the order of 2 rows.
|
||||
|
||||
The list of rules is ordered from top to bottom: 0,1,2,3...
|
||||
so a click on the down button sums +1, a click on the up button rest -1
|
||||
"""
|
||||
node = self._nodes.get_node(addr)
|
||||
if node == None:
|
||||
return
|
||||
if not 'firewall' in node:
|
||||
return
|
||||
for sdx, c in enumerate(node['firewall'].SystemRules):
|
||||
for cdx, u in enumerate(c.Chains):
|
||||
nrules = len(u.Rules)
|
||||
for rdx, r in enumerate(u.Rules):
|
||||
# is the last rule
|
||||
if new_pos > nrules and new_pos < nrules:
|
||||
break
|
||||
if u.Rules[rdx].UUID == uuid:
|
||||
old_rule = u.Rules[old_pos]
|
||||
new_rule = ui_pb2.FwRule()
|
||||
new_rule.CopyFrom(u.Rules[new_pos])
|
||||
|
||||
node['firewall'].SystemRules[sdx].Chains[cdx].Rules[new_pos].CopyFrom(old_rule)
|
||||
node['firewall'].SystemRules[sdx].Chains[cdx].Rules[old_pos].CopyFrom(new_rule)
|
||||
|
||||
self._nodes.add_fw_config(addr, node['firewall'])
|
||||
#self._nodes.add_fw_rules(addr, node['fwrules'])
|
||||
|
||||
self.rulesUpdated.emit()
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_duplicated(self, addr, orig_rule):
|
||||
# we need to duplicate the rule, otherwise we'd modify the UUID of the
|
||||
# orig rule.
|
||||
temp_c = ui_pb2.FwChain()
|
||||
temp_c.CopyFrom(orig_rule)
|
||||
# the UUID will be different, so zero it out.
|
||||
temp_c.Rules[0].UUID = ""
|
||||
node = self._nodes.get_node(addr)
|
||||
if node == None:
|
||||
return False
|
||||
if not 'firewall' in node:
|
||||
return False
|
||||
for n in node['firewall'].SystemRules:
|
||||
for c in n.Chains:
|
||||
if c.Name == temp_c.Name and \
|
||||
c.Hook == temp_c.Hook and \
|
||||
c.Table == temp_c.Table and \
|
||||
c.Family == temp_c.Family and \
|
||||
c.Type == temp_c.Type:
|
||||
for rdx, r in enumerate(c.Rules):
|
||||
uuid = c.Rules[rdx].UUID
|
||||
c.Rules[rdx].UUID = ""
|
||||
is_equal = c.Rules[rdx].SerializeToString() == temp_c.Rules[0].SerializeToString()
|
||||
c.Rules[rdx].UUID = uuid
|
||||
|
||||
if is_equal:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def new(
|
||||
enabled=True,
|
||||
_uuid="",
|
||||
description="",
|
||||
expressions=None,
|
||||
target="",
|
||||
target_parms=""
|
||||
):
|
||||
rule = ui_pb2.FwRule()
|
||||
if _uuid == "":
|
||||
rule.UUID = str(uuid.uuid1())
|
||||
else:
|
||||
rule.UUID = _uuid
|
||||
rule.Enabled = enabled
|
||||
rule.Description = description
|
||||
if expressions != None:
|
||||
rule.Expressions.append(expressions)
|
||||
rule.Target = target
|
||||
rule.TargetParameters = target_parms
|
||||
|
||||
return rule
|
||||
|
||||
@staticmethod
|
||||
def new_flat(c, r):
|
||||
"""Create a new "flat" rule from a hierarchical one.
|
||||
Transform from:
|
||||
{
|
||||
xx:
|
||||
{
|
||||
yy: {
|
||||
to:
|
||||
{xx:, yy}
|
||||
"""
|
||||
|
||||
chain = ui_pb2.FwChain()
|
||||
chain.CopyFrom(c)
|
||||
del chain.Rules[:]
|
||||
chain.Rules.extend([r])
|
||||
|
||||
return chain
|
||||
|
||||
@staticmethod
|
||||
def to_dict(sysRules):
|
||||
"""Transform json/protobuf struct to flat structure.
|
||||
This is the default format used to find rules in the table view.
|
||||
"""
|
||||
rules={}
|
||||
for s in sysRules:
|
||||
for c in s.Chains:
|
||||
if len(c.Rules) == 0:
|
||||
continue
|
||||
for r in c.Rules:
|
||||
rules[r.UUID] = Rules.new_flat(c, r)
|
||||
|
||||
return rules
|
||||
|
||||
@staticmethod
|
||||
def to_array(addr, chain, rule):
|
||||
cols = []
|
||||
cols.append(rule.UUID)
|
||||
cols.append(addr)
|
||||
cols.append(chain.Name)
|
||||
cols.append(chain.Table)
|
||||
cols.append(chain.Family)
|
||||
cols.append(chain.Hook)
|
||||
cols.append(str(rule.Enabled))
|
||||
cols.append(rule.Description)
|
||||
exprs = ""
|
||||
for e in rule.Expressions:
|
||||
exprs += "{0} {1} {2}".format(
|
||||
e.Statement.Op,
|
||||
e.Statement.Name,
|
||||
"".join(["{0} {1} ".format(h.Key, h.Value) for h in e.Statement.Values ])
|
||||
)
|
||||
cols.append(exprs)
|
||||
cols.append(rule.Target)
|
||||
|
||||
return cols
|
24
ui/opensnitch/firewall/utils.py
Normal file
24
ui/opensnitch/firewall/utils.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
|
||||
from google.protobuf import __version__ as protobuf_version
|
||||
from .enums import *
|
||||
|
||||
class Utils():
|
||||
|
||||
@staticmethod
|
||||
def isExprPort(value):
|
||||
"""Return true if the value is valid for a port based rule:
|
||||
nft add rule ... tcp dport 22 accept
|
||||
"""
|
||||
return value == Statements.TCP.value or \
|
||||
value == Statements.UDP.value or \
|
||||
value == Statements.UDPLITE.value or \
|
||||
value == Statements.SCTP.value or \
|
||||
value == Statements.DCCP.value
|
||||
|
||||
@staticmethod
|
||||
def isProtobufSupported():
|
||||
"""
|
||||
The protobuf operations append() and insert() were introduced on 3.8.0 version.
|
||||
"""
|
||||
vparts = protobuf_version.split(".")
|
||||
return int(vparts[0]) >= 3 and int(vparts[1]) >= 8
|
|
@ -1,3 +1,4 @@
|
|||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
|
||||
from queue import Queue
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
@ -7,8 +8,10 @@ from opensnitch import ui_pb2
|
|||
from opensnitch.database import Database
|
||||
from opensnitch.config import Config
|
||||
|
||||
class Nodes():
|
||||
class Nodes(QObject):
|
||||
__instance = None
|
||||
nodesUpdated = pyqtSignal(int) # total
|
||||
|
||||
LOG_TAG = "[Nodes]: "
|
||||
ONLINE = "\u2713 online"
|
||||
OFFLINE = "\u2613 offline"
|
||||
|
@ -21,6 +24,7 @@ class Nodes():
|
|||
return Nodes.__instance
|
||||
|
||||
def __init__(self):
|
||||
QObject.__init__(self)
|
||||
self._db = Database.instance()
|
||||
self._nodes = {}
|
||||
self._notifications_sent = {}
|
||||
|
@ -45,18 +49,27 @@ class Nodes():
|
|||
self.add_data(addr, client_config)
|
||||
self.update(proto, _addr)
|
||||
|
||||
return self._nodes[addr]
|
||||
self.nodesUpdated.emit(self.count())
|
||||
|
||||
return self._nodes[addr], addr
|
||||
|
||||
except Exception as e:
|
||||
print(self.LOG_TAG, "exception adding/updating node: ", e, "addr:", addr, "config:", client_config)
|
||||
|
||||
return None
|
||||
return None, None
|
||||
|
||||
def add_data(self, addr, client_config):
|
||||
if client_config != None:
|
||||
self._nodes[addr]['data'] = self.get_client_config(client_config)
|
||||
self.add_fw_config(addr, client_config.systemFirewall)
|
||||
self.add_rules(addr, client_config.rules)
|
||||
|
||||
def add_fw_config(self, addr, fwconfig):
|
||||
self._nodes[addr]['firewall'] = fwconfig
|
||||
|
||||
def add_fw_rules(self, addr, fwconfig):
|
||||
self._nodes[addr]['fwrules'] = fwconfig
|
||||
|
||||
def add_rule(self, time, node, name, enabled, precedence, action, duration, op_type, op_sensitive, op_operand, op_data):
|
||||
# don't add rule if the user has selected to exclude temporary
|
||||
# rules
|
||||
|
@ -92,6 +105,7 @@ class Nodes():
|
|||
def delete_all(self):
|
||||
self.send_notifications(None)
|
||||
self._nodes = {}
|
||||
self.nodesUpdated.emit(self.count())
|
||||
|
||||
def delete(self, peer):
|
||||
proto, addr = self.get_addr(peer)
|
||||
|
@ -101,6 +115,7 @@ class Nodes():
|
|||
self._nodes[addr]['notifications'].put(None)
|
||||
if addr in self._nodes:
|
||||
del self._nodes[addr]
|
||||
self.nodesUpdated.emit(self.count())
|
||||
|
||||
def get(self):
|
||||
return self._nodes
|
||||
|
@ -170,12 +185,12 @@ class Nodes():
|
|||
print(self.LOG_TAG + " exception saving nodes config: ", e, config)
|
||||
|
||||
def start_interception(self, _addr=None, _callback=None):
|
||||
return self.firewall(not_type=ui_pb2.LOAD_FIREWALL, addr=_addr, callback=_callback)
|
||||
return self.firewall(not_type=ui_pb2.ENABLE_INTERCEPTION, addr=_addr, callback=_callback)
|
||||
|
||||
def stop_interception(self, _addr=None, _callback=None):
|
||||
return self.firewall(not_type=ui_pb2.UNLOAD_FIREWALL, addr=_addr, callback=_callback)
|
||||
return self.firewall(not_type=ui_pb2.DISABLE_INTERCEPTION, addr=_addr, callback=_callback)
|
||||
|
||||
def firewall(self, not_type=ui_pb2.LOAD_FIREWALL, addr=None, callback=None):
|
||||
def firewall(self, not_type=ui_pb2.ENABLE_INTERCEPTION, addr=None, callback=None):
|
||||
noti = ui_pb2.Notification(clientName="", serverName="", type=not_type, data="", rules=[])
|
||||
if addr == None:
|
||||
nid = self.send_notifications(noti, callback)
|
||||
|
@ -236,20 +251,23 @@ class Nodes():
|
|||
return notification.id
|
||||
|
||||
def reply_notification(self, addr, reply):
|
||||
if reply == None:
|
||||
print(self.LOG_TAG, " reply notification None")
|
||||
return
|
||||
try:
|
||||
if reply == None:
|
||||
print(self.LOG_TAG, " reply notification None")
|
||||
return
|
||||
|
||||
if reply.id in self._notifications_sent:
|
||||
if self._notifications_sent[reply.id] != None:
|
||||
if self._notifications_sent[reply.id]['callback'] != None:
|
||||
self._notifications_sent[reply.id]['callback'].emit(reply)
|
||||
if reply.id in self._notifications_sent:
|
||||
if self._notifications_sent[reply.id] != None:
|
||||
if self._notifications_sent[reply.id]['callback'] != None:
|
||||
self._notifications_sent[reply.id]['callback'].emit(reply)
|
||||
|
||||
# delete only one-time notifications
|
||||
# we need the ID of streaming notifications from the server
|
||||
# (monitor_process for example) to keep track of the data sent to us.
|
||||
if self._notifications_sent[reply.id]['type'] != ui_pb2.MONITOR_PROCESS:
|
||||
del self._notifications_sent[reply.id]
|
||||
# delete only one-time notifications
|
||||
# we need the ID of streaming notifications from the server
|
||||
# (monitor_process for example) to keep track of the data sent to us.
|
||||
if self._notifications_sent[reply.id]['type'] != ui_pb2.MONITOR_PROCESS:
|
||||
del self._notifications_sent[reply.id]
|
||||
except Exception as e:
|
||||
print(self.LOG_TAG, "notification exception:", e)
|
||||
|
||||
def update(self, proto, addr, status=ONLINE):
|
||||
try:
|
||||
|
@ -300,3 +318,12 @@ class Nodes():
|
|||
self._db.delete_rule(rule.name, addr)
|
||||
|
||||
return nid, noti
|
||||
|
||||
def reload_fw(self, addr, fw_config, callback):
|
||||
notif = ui_pb2.Notification(
|
||||
id=int(str(time.time()).replace(".", "")),
|
||||
type=ui_pb2.RELOAD_FW_RULES,
|
||||
sysFirewall=fw_config
|
||||
)
|
||||
nid = self.send_notification(addr, notif, callback)
|
||||
return nid, notif
|
||||
|
|
470
ui/opensnitch/res/firewall.ui
Normal file
470
ui/opensnitch/res/firewall.ui
Normal file
|
@ -0,0 +1,470 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>440</width>
|
||||
<height>463</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Firewall</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QTabBar {
|
||||
alignment: center;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="tabPosition">
|
||||
<enum>QTabWidget::South</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="documentMode">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="tabMain">
|
||||
<attribute name="title">
|
||||
<string/>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-size:14pt; font-weight:600;">Firewall</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="sliderFwEnable">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QSlider::groove:horizontal {
|
||||
background-color: transparent;
|
||||
border: 0px solid #424242;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
QSlider::handle:horizontal {
|
||||
background-color: rgb(154, 153, 150);
|
||||
border: 1px solid rgb(119, 118, 123);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px; /* do not delete this */
|
||||
/*margin-left: -5px;*/
|
||||
margin-top: -2px;
|
||||
margin-bottom: -2px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
QSlider::handle:horizontal:hover {
|
||||
background-color: rgb(79, 91, 98);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
QSlider::add-page:horizontal {
|
||||
border-radius: 4px;
|
||||
background: rgb(237, 51, 59);
|
||||
border: 1px solid rgb(192, 28, 40);
|
||||
}
|
||||
|
||||
QSlider::sub-page:horizontal {
|
||||
border-radius: 4px;
|
||||
background: rgb(139, 195, 74);
|
||||
border: 1px solid rgb(67, 190, 24);
|
||||
}</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="invertedAppearance">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBothSides</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblStatusIcon">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::AutoText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>96</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblFwStatus">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(237, 51, 59);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="policiesBox">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="comboOutput">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Allow</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="emblem-default">
|
||||
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Deny</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="process-stop">
|
||||
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="comboInput">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Allow</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="emblem-default">
|
||||
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Deny</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="process-stop">
|
||||
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Outbound</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cmdNewRule">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>New rule</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-new">
|
||||
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cmdExcludeService">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Exclude a service from being intercepted</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Exclude service</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-jump">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Inbound</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblProfile">
|
||||
<property name="text">
|
||||
<string>Profile</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboProfile"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cmdClose">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="window-close">
|
||||
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>sliderFwEnable</tabstop>
|
||||
<tabstop>comboInput</tabstop>
|
||||
<tabstop>comboOutput</tabstop>
|
||||
<tabstop>cmdNewRule</tabstop>
|
||||
<tabstop>cmdExcludeService</tabstop>
|
||||
<tabstop>cmdClose</tabstop>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
418
ui/opensnitch/res/firewall_rule.ui
Normal file
418
ui/opensnitch/res/firewall_rule.ui
Normal file
|
@ -0,0 +1,418 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>424</width>
|
||||
<height>462</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Firewall rule</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup/icon-white.svg</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup/icon-white.svg</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QTabBar {
|
||||
alignment: center;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="elideMode">
|
||||
<enum>Qt::ElideRight</enum>
|
||||
</property>
|
||||
<property name="documentMode">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Simple</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Node</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboNodes"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkEnable">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMaximumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineDescription">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Port</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboOperator">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentText">
|
||||
<string notr="true">equal</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>equal</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>not equal</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>greater or equal than</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>greater than</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>less or equal than</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>less than</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboPorts">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="currentText">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frameDirection">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Direction</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboDirection">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>IN</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-down">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>OUT</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-up">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frameAction">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Action</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboVerdict">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>ACCEPT</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="object-select-symbolic">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>DROP</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="window-close">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>REJECT</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-delete">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RETURN</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-undo">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Advanced</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>TODO</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="helpButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>You can use , or - to specify multiple ports or a port range: 22 or 22,443 or 10000-20000</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="help-browser"/>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cmdClose">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="window-close">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cmdDelete">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-delete"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cmdSave">
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cmdAdd">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -10,7 +10,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>863</width>
|
||||
<height>600</height>
|
||||
<height>597</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -108,7 +108,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="emblem-default">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -117,7 +117,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="emblem-important">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -126,7 +126,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="window-close">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
|
@ -167,7 +167,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-previous">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -185,7 +185,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-next">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -194,6 +194,9 @@
|
|||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>50</string>
|
||||
|
@ -231,7 +234,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-clear-all">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
|
@ -245,7 +248,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="help-browser">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -288,7 +291,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
|
@ -311,7 +314,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="preferences-system">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
|
@ -334,7 +337,20 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-new">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="fwButton">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="security-high"/>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
|
@ -439,7 +455,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="media-playback-start">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
@ -474,7 +490,7 @@
|
|||
<widget class="QWidget" name="tab">
|
||||
<attribute name="icon">
|
||||
<iconset theme="view-sort-ascending">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Events</string>
|
||||
|
@ -545,7 +561,7 @@
|
|||
<widget class="QWidget" name="tab_8">
|
||||
<attribute name="icon">
|
||||
<iconset theme="network-workgroup">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Nodes</string>
|
||||
|
@ -572,7 +588,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-previous">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -641,7 +657,7 @@
|
|||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="icon">
|
||||
<iconset theme="address-book-new">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Rules</string>
|
||||
|
@ -665,7 +681,13 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<property name="animated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
|
@ -673,6 +695,11 @@
|
|||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Application rules</string>
|
||||
|
@ -686,7 +713,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="system-run">
|
||||
<normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
|
@ -699,7 +726,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="security-medium">
|
||||
<normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -713,7 +740,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-clear">
|
||||
<normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</item>
|
||||
|
@ -729,8 +756,23 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="system">
|
||||
<normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
|
||||
<iconset theme="network-workgroup">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>System rules</string>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="security-high">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
|
@ -739,6 +781,40 @@
|
|||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="FirewallTableView" name="fwTable">
|
||||
<property name="autoScroll">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed</set>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderMinimumSectionSize">
|
||||
<number>25</number>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderDefaultSectionSize">
|
||||
<number>42</number>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="GenericTableView" name="rulesTable">
|
||||
<property name="selectionBehavior">
|
||||
|
@ -772,7 +848,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="system-run">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -781,7 +857,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="security-medium">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -790,7 +866,15 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-clear">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>System rules</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="security-high"/>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
|
@ -821,7 +905,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-previous">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -842,7 +926,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="accessories-text-editor">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -856,7 +940,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-delete">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -887,7 +971,7 @@
|
|||
<widget class="QWidget" name="tab_4">
|
||||
<attribute name="icon">
|
||||
<iconset theme="computer">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Hosts</string>
|
||||
|
@ -911,7 +995,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-previous">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -974,7 +1058,7 @@
|
|||
<widget class="QWidget" name="tab_7">
|
||||
<attribute name="icon">
|
||||
<iconset theme="system-run">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Applications</string>
|
||||
|
@ -1001,7 +1085,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-previous">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -1012,7 +1096,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="system-search">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -1081,7 +1165,7 @@
|
|||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="icon">
|
||||
<iconset theme="emblem-web">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Addresses</string>
|
||||
|
@ -1105,7 +1189,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-previous">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -1168,7 +1252,7 @@
|
|||
<widget class="QWidget" name="tab_5">
|
||||
<attribute name="icon">
|
||||
<iconset theme="network-wired">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Ports</string>
|
||||
|
@ -1192,7 +1276,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-previous">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -1252,7 +1336,7 @@
|
|||
<widget class="QWidget" name="tab_6">
|
||||
<attribute name="icon">
|
||||
<iconset theme="system-users">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Users</string>
|
||||
|
@ -1276,7 +1360,7 @@
|
|||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-previous">
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
<normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -1628,6 +1712,11 @@
|
|||
<extends>QTableView</extends>
|
||||
<header>customwidgets.generictableview</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>FirewallTableView</class>
|
||||
<extends>QTableView</extends>
|
||||
<header>customwidgets.firewalltableview</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="resources.qrc"/>
|
||||
|
|
|
@ -17,6 +17,7 @@ from opensnitch.dialogs.prompt import PromptDialog
|
|||
from opensnitch.dialogs.stats import StatsDialog
|
||||
|
||||
from opensnitch.notifications import DesktopNotifications
|
||||
from opensnitch.firewall import Rules as FwRules
|
||||
from opensnitch.nodes import Nodes
|
||||
from opensnitch.config import Config
|
||||
from opensnitch.version import version
|
||||
|
@ -530,8 +531,12 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
@QtCore.pyqtSlot(dict)
|
||||
def _on_node_actions(self, kwargs):
|
||||
if kwargs['action'] == self.NODE_ADD:
|
||||
n = self._nodes.add(kwargs['peer'], kwargs['node_config'])
|
||||
n, addr = self._nodes.add(kwargs['peer'], kwargs['node_config'])
|
||||
if n != None:
|
||||
self._nodes.add_fw_rules(
|
||||
addr,
|
||||
FwRules.to_dict(kwargs['node_config'].systemFirewall.SystemRules)
|
||||
)
|
||||
self._status_change_trigger.emit(True)
|
||||
# if there're more than one node, we can't update the status
|
||||
# based on the fw status, only if the daemon is running or not
|
||||
|
|
|
@ -10,6 +10,8 @@ import fcntl
|
|||
import struct
|
||||
import array
|
||||
import os, sys, glob
|
||||
import enum
|
||||
import re
|
||||
|
||||
class AsnDB():
|
||||
__instance = None
|
||||
|
@ -289,3 +291,58 @@ class FileDialog():
|
|||
fileName = QtWidgets.QFileDialog.getExistingDirectory(parent, "", current_dir, options)
|
||||
return fileName
|
||||
|
||||
# https://stackoverflow.com/questions/29503339/how-to-get-all-values-from-python-enum-class
|
||||
class Enums(enum.Enum):
|
||||
@classmethod
|
||||
def to_dict(cls):
|
||||
return {e.name: e.value for e in cls}
|
||||
|
||||
@classmethod
|
||||
def keys(cls):
|
||||
return cls._member_names_
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
return [v.value for v in cls]
|
||||
|
||||
class NetworkServices():
|
||||
"""Get a list of known ports. /etc/services
|
||||
"""
|
||||
__instance = None
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
if NetworkServices.__instance == None:
|
||||
NetworkServices.__instance = NetworkServices()
|
||||
return Services.__instance
|
||||
|
||||
srv_array = []
|
||||
ports_list = []
|
||||
|
||||
def __init__(self):
|
||||
etcServices = open("/etc/services")
|
||||
for line in etcServices:
|
||||
if line[0] == "#":
|
||||
continue
|
||||
g = re.search("([a-zA-Z0-9\-]+)( |\t)+([0-9]+)\/([a-zA-Z0-9\-]+)(.*)\n", line)
|
||||
if g:
|
||||
self.srv_array.append("{0}/{1} {2}".format(
|
||||
g.group(1),
|
||||
g.group(3),
|
||||
"" if len(g.groups())>3 and g.group(4) == "" else "({0})".format(g.group(4).replace("\t", ""))
|
||||
)
|
||||
)
|
||||
self.ports_list.append(g.group(3))
|
||||
|
||||
# extra ports that don't exist in /etc/services
|
||||
self.srv_array.append("wireguard/51820 WireGuard VPN")
|
||||
self.ports_list.append("51820")
|
||||
|
||||
def to_array(self):
|
||||
return self.srv_array
|
||||
|
||||
def port_by_index(self, idx):
|
||||
return self.ports_list[idx]
|
||||
|
||||
def index_by_port(self, port):
|
||||
return self.ports_list.index(str(port))
|
||||
|
|
Loading…
Add table
Reference in a new issue