Feat linux firewall support (#805)

Update the client's engine to apply firewall rules received from the manager (results of ACL policy).
This commit is contained in:
Givi Khojanashvili 2023-05-29 18:00:18 +04:00 committed by GitHub
parent 2eb9a97fee
commit ba7a39a4fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 4143 additions and 1013 deletions

View File

@ -78,6 +78,9 @@ jobs:
- name: Generate RouteManager Test bin
run: CGO_ENABLED=0 go test -c -o routemanager-testing.bin ./client/internal/routemanager/...
- name: Generate nftables Manager Test bin
run: CGO_ENABLED=0 go test -c -o nftablesmanager-testing.bin ./client/firewall/nftables/...
- name: Generate Engine Test bin
run: CGO_ENABLED=0 go test -c -o engine-testing.bin ./client/internal
@ -96,6 +99,9 @@ jobs:
- name: Run RouteManager tests in docker
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/routemanager --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/routemanager-testing.bin -test.timeout 5m -test.parallel 1
- name: Run nftables Manager tests in docker
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/firewall --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/nftablesmanager-testing.bin -test.timeout 5m -test.parallel 1
- name: Run Engine tests in docker
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/engine-testing.bin -test.timeout 5m -test.parallel 1

View File

@ -13,22 +13,24 @@ type Rule interface {
GetRuleID() string
}
// Direction is the direction of the traffic
type Direction int
// RuleDirection is the traffic direction which a rule is applied
type RuleDirection int
const (
// DirectionSrc is the direction of the traffic from the source
DirectionSrc Direction = iota
// DirectionDst is the direction of the traffic from the destination
DirectionDst
// RuleDirectionIN applies to filters that handlers incoming traffic
RuleDirectionIN RuleDirection = iota
// RuleDirectionOUT applies to filters that handlers outgoing traffic
RuleDirectionOUT
)
// Action is the action to be taken on a rule
type Action int
const (
// ActionUnknown is a unknown action
ActionUnknown Action = iota
// ActionAccept is the action to accept a packet
ActionAccept Action = iota
ActionAccept
// ActionDrop is the action to drop a packet
ActionDrop
)
@ -39,10 +41,15 @@ const (
// Netbird client for ACL and routing functionality
type Manager interface {
// AddFiltering rule to the firewall
//
// If comment argument is empty firewall manager should set
// rule ID as comment for the rule
AddFiltering(
ip net.IP,
port *Port,
direction Direction,
proto Protocol,
sPort *Port,
dPort *Port,
direction RuleDirection,
action Action,
comment string,
) (Rule, error)

View File

@ -8,26 +8,43 @@ import (
"github.com/coreos/go-iptables/iptables"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
fw "github.com/netbirdio/netbird/client/firewall"
)
const (
// ChainFilterName is the name of the chain that is used for filtering by the Netbird client
ChainFilterName = "NETBIRD-ACL"
// ChainInputFilterName is the name of the chain that is used for filtering incoming packets
ChainInputFilterName = "NETBIRD-ACL-INPUT"
// ChainOutputFilterName is the name of the chain that is used for filtering outgoing packets
ChainOutputFilterName = "NETBIRD-ACL-OUTPUT"
)
// jumpNetbirdInputDefaultRule always added by manager to the input chain for all trafic from the Netbird interface
var jumpNetbirdInputDefaultRule = []string{"-j", ChainInputFilterName}
// jumpNetbirdOutputDefaultRule always added by manager to the output chain for all trafic from the Netbird interface
var jumpNetbirdOutputDefaultRule = []string{"-j", ChainOutputFilterName}
// dropAllDefaultRule in the Netbird chain
var dropAllDefaultRule = []string{"-j", "DROP"}
// Manager of iptables firewall
type Manager struct {
mutex sync.Mutex
ipv4Client *iptables.IPTables
ipv6Client *iptables.IPTables
wgIfaceName string
}
// Create iptables firewall manager
func Create() (*Manager, error) {
m := &Manager{}
func Create(wgIfaceName string) (*Manager, error) {
m := &Manager{
wgIfaceName: wgIfaceName,
}
// init clients for booth ipv4 and ipv6
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
@ -38,118 +55,266 @@ func Create() (*Manager, error) {
ipv6Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
if err != nil {
return nil, fmt.Errorf("ip6tables is not installed in the system or not supported")
log.Errorf("ip6tables is not installed in the system or not supported: %v", err)
} else {
m.ipv6Client = ipv6Client
}
m.ipv6Client = ipv6Client
if err := m.Reset(); err != nil {
return nil, fmt.Errorf("failed to reset firewall: %s", err)
return nil, fmt.Errorf("failed to reset firewall: %v", err)
}
return m, nil
}
// AddFiltering rule to the firewall
//
// If comment is empty rule ID is used as comment
func (m *Manager) AddFiltering(
ip net.IP,
port *fw.Port,
direction fw.Direction,
protocol fw.Protocol,
sPort *fw.Port,
dPort *fw.Port,
direction fw.RuleDirection,
action fw.Action,
comment string,
) (fw.Rule, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
client := m.client(ip)
ok, err := client.ChainExists("filter", ChainFilterName)
client, err := m.client(ip)
if err != nil {
return nil, fmt.Errorf("failed to check if chain exists: %s", err)
}
if !ok {
if err := client.NewChain("filter", ChainFilterName); err != nil {
return nil, fmt.Errorf("failed to create chain: %s", err)
}
}
if port == nil || port.Values == nil || (port.IsRange && len(port.Values) != 2) {
return nil, fmt.Errorf("invalid port definition")
}
pv := strconv.Itoa(port.Values[0])
if port.IsRange {
pv += ":" + strconv.Itoa(port.Values[1])
}
specs := m.filterRuleSpecs("filter", ChainFilterName, ip, pv, direction, action, comment)
if err := client.AppendUnique("filter", ChainFilterName, specs...); err != nil {
return nil, err
}
rule := &Rule{
id: uuid.New().String(),
specs: specs,
v6: ip.To4() == nil,
var dPortVal, sPortVal string
if dPort != nil && dPort.Values != nil {
// TODO: we support only one port per rule in current implementation of ACLs
dPortVal = strconv.Itoa(dPort.Values[0])
}
return rule, nil
if sPort != nil && sPort.Values != nil {
sPortVal = strconv.Itoa(sPort.Values[0])
}
ruleID := uuid.New().String()
if comment == "" {
comment = ruleID
}
specs := m.filterRuleSpecs(
"filter",
ip,
string(protocol),
sPortVal,
dPortVal,
direction,
action,
comment,
)
if direction == fw.RuleDirectionOUT {
ok, err := client.Exists("filter", ChainOutputFilterName, specs...)
if err != nil {
return nil, fmt.Errorf("check is output rule already exists: %w", err)
}
if ok {
return nil, fmt.Errorf("input rule already exists")
}
if err := client.Insert("filter", ChainOutputFilterName, 1, specs...); err != nil {
return nil, err
}
} else {
ok, err := client.Exists("filter", ChainInputFilterName, specs...)
if err != nil {
return nil, fmt.Errorf("check is input rule already exists: %w", err)
}
if ok {
return nil, fmt.Errorf("input rule already exists")
}
if err := client.Insert("filter", ChainInputFilterName, 1, specs...); err != nil {
return nil, err
}
}
return &Rule{
id: ruleID,
specs: specs,
dst: direction == fw.RuleDirectionOUT,
v6: ip.To4() == nil,
}, nil
}
// DeleteRule from the firewall by rule definition
func (m *Manager) DeleteRule(rule fw.Rule) error {
m.mutex.Lock()
defer m.mutex.Unlock()
r, ok := rule.(*Rule)
if !ok {
return fmt.Errorf("invalid rule type")
}
client := m.ipv4Client
if r.v6 {
if m.ipv6Client == nil {
return fmt.Errorf("ipv6 is not supported")
}
client = m.ipv6Client
}
return client.Delete("filter", ChainFilterName, r.specs...)
if r.dst {
return client.Delete("filter", ChainOutputFilterName, r.specs...)
}
return client.Delete("filter", ChainInputFilterName, r.specs...)
}
// Reset firewall to the default state
func (m *Manager) Reset() error {
m.mutex.Lock()
defer m.mutex.Unlock()
if err := m.reset(m.ipv4Client, "filter", ChainFilterName); err != nil {
return fmt.Errorf("clean ipv4 firewall ACL chain: %w", err)
if err := m.reset(m.ipv4Client, "filter"); err != nil {
return fmt.Errorf("clean ipv4 firewall ACL input chain: %w", err)
}
if err := m.reset(m.ipv6Client, "filter", ChainFilterName); err != nil {
return fmt.Errorf("clean ipv6 firewall ACL chain: %w", err)
if m.ipv6Client != nil {
if err := m.reset(m.ipv6Client, "filter"); err != nil {
return fmt.Errorf("clean ipv6 firewall ACL input chain: %w", err)
}
}
return nil
}
// reset firewall chain, clear it and drop it
func (m *Manager) reset(client *iptables.IPTables, table, chain string) error {
ok, err := client.ChainExists(table, chain)
func (m *Manager) reset(client *iptables.IPTables, table string) error {
ok, err := client.ChainExists(table, ChainInputFilterName)
if err != nil {
return fmt.Errorf("failed to check if chain exists: %w", err)
return fmt.Errorf("failed to check if input chain exists: %w", err)
}
if !ok {
if ok {
specs := append([]string{"-i", m.wgIfaceName}, jumpNetbirdInputDefaultRule...)
if ok, err := client.Exists("filter", "INPUT", specs...); err != nil {
return err
} else if ok {
if err := client.Delete("filter", "INPUT", specs...); err != nil {
log.WithError(err).Errorf("failed to delete default input rule: %v", err)
}
}
}
ok, err = client.ChainExists(table, ChainOutputFilterName)
if err != nil {
return fmt.Errorf("failed to check if output chain exists: %w", err)
}
if ok {
specs := append([]string{"-o", m.wgIfaceName}, jumpNetbirdOutputDefaultRule...)
if ok, err := client.Exists("filter", "OUTPUT", specs...); err != nil {
return err
} else if ok {
if err := client.Delete("filter", "OUTPUT", specs...); err != nil {
log.WithError(err).Errorf("failed to delete default output rule: %v", err)
}
}
}
if err := client.ClearAndDeleteChain(table, ChainInputFilterName); err != nil {
log.Errorf("failed to clear and delete input chain: %v", err)
return nil
}
if err := client.ClearChain(table, ChainFilterName); err != nil {
return fmt.Errorf("failed to clear chain: %w", err)
if err := client.ClearAndDeleteChain(table, ChainOutputFilterName); err != nil {
log.Errorf("failed to clear and delete input chain: %v", err)
return nil
}
return client.DeleteChain(table, ChainFilterName)
return nil
}
// filterRuleSpecs returns the specs of a filtering rule
func (m *Manager) filterRuleSpecs(
table string, chain string, ip net.IP, port string,
direction fw.Direction, action fw.Action, comment string,
table string, ip net.IP, protocol string, sPort, dPort string,
direction fw.RuleDirection, action fw.Action, comment string,
) (specs []string) {
if direction == fw.DirectionSrc {
switch direction {
case fw.RuleDirectionIN:
specs = append(specs, "-s", ip.String())
case fw.RuleDirectionOUT:
specs = append(specs, "-d", ip.String())
}
if protocol != "all" {
specs = append(specs, "-p", protocol)
}
if sPort != "" {
specs = append(specs, "--sport", sPort)
}
if dPort != "" {
specs = append(specs, "--dport", dPort)
}
specs = append(specs, "-p", "tcp", "--dport", port)
specs = append(specs, "-j", m.actionToStr(action))
return append(specs, "-m", "comment", "--comment", comment)
}
// client returns corresponding iptables client for the given ip
func (m *Manager) client(ip net.IP) *iptables.IPTables {
// rawClient returns corresponding iptables client for the given ip
func (m *Manager) rawClient(ip net.IP) (*iptables.IPTables, error) {
if ip.To4() != nil {
return m.ipv4Client
return m.ipv4Client, nil
}
return m.ipv6Client
if m.ipv6Client == nil {
return nil, fmt.Errorf("ipv6 is not supported")
}
return m.ipv6Client, nil
}
// client returns client with initialized chain and default rules
func (m *Manager) client(ip net.IP) (*iptables.IPTables, error) {
client, err := m.rawClient(ip)
if err != nil {
return nil, err
}
ok, err := client.ChainExists("filter", ChainInputFilterName)
if err != nil {
return nil, fmt.Errorf("failed to check if chain exists: %w", err)
}
if !ok {
if err := client.NewChain("filter", ChainInputFilterName); err != nil {
return nil, fmt.Errorf("failed to create input chain: %w", err)
}
if err := client.AppendUnique("filter", ChainInputFilterName, dropAllDefaultRule...); err != nil {
return nil, fmt.Errorf("failed to create default drop all in netbird input chain: %w", err)
}
specs := append([]string{"-i", m.wgIfaceName}, jumpNetbirdInputDefaultRule...)
if err := client.AppendUnique("filter", "INPUT", specs...); err != nil {
return nil, fmt.Errorf("failed to create input chain jump rule: %w", err)
}
}
ok, err = client.ChainExists("filter", ChainOutputFilterName)
if err != nil {
return nil, fmt.Errorf("failed to check if chain exists: %w", err)
}
if !ok {
if err := client.NewChain("filter", ChainOutputFilterName); err != nil {
return nil, fmt.Errorf("failed to create output chain: %w", err)
}
if err := client.AppendUnique("filter", ChainOutputFilterName, dropAllDefaultRule...); err != nil {
return nil, fmt.Errorf("failed to create default drop all in netbird output chain: %w", err)
}
specs := append([]string{"-o", m.wgIfaceName}, jumpNetbirdOutputDefaultRule...)
if err := client.AppendUnique("filter", "OUTPUT", specs...); err != nil {
return nil, fmt.Errorf("failed to create output chain jump rule: %w", err)
}
}
return client, nil
}
func (m *Manager) actionToStr(action fw.Action) string {

View File

@ -1,105 +1,129 @@
package iptables
import (
"fmt"
"net"
"testing"
"time"
"github.com/coreos/go-iptables/iptables"
"github.com/stretchr/testify/require"
fw "github.com/netbirdio/netbird/client/firewall"
)
func TestNewManager(t *testing.T) {
func TestIptablesManager(t *testing.T) {
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
manager, err := Create()
if err != nil {
t.Fatal(err)
}
// just check on the local interface
manager, err := Create("lo")
require.NoError(t, err)
time.Sleep(time.Second)
defer func() {
if err := manager.Reset(); err != nil {
t.Errorf("clear the manager state: %v", err)
}
time.Sleep(time.Second)
}()
var rule1 fw.Rule
t.Run("add first rule", func(t *testing.T) {
ip := net.ParseIP("10.20.0.2")
port := &fw.Port{Proto: fw.PortProtocolTCP, Values: []int{8080}}
rule1, err = manager.AddFiltering(ip, port, fw.DirectionDst, fw.ActionAccept, "accept HTTP traffic")
if err != nil {
t.Errorf("failed to add rule: %v", err)
}
port := &fw.Port{Values: []int{8080}}
rule1, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionOUT, fw.ActionAccept, "accept HTTP traffic")
require.NoError(t, err, "failed to add rule")
checkRuleSpecs(t, ipv4Client, true, rule1.(*Rule).specs...)
checkRuleSpecs(t, ipv4Client, ChainOutputFilterName, true, rule1.(*Rule).specs...)
})
var rule2 fw.Rule
t.Run("add second rule", func(t *testing.T) {
ip := net.ParseIP("10.20.0.3")
port := &fw.Port{
Proto: fw.PortProtocolTCP,
Values: []int{8043: 8046},
}
rule2, err = manager.AddFiltering(
ip, port, fw.DirectionDst, fw.ActionAccept, "accept HTTPS traffic from ports range")
if err != nil {
t.Errorf("failed to add rule: %v", err)
}
ip, "tcp", port, nil, fw.RuleDirectionIN, fw.ActionAccept, "accept HTTPS traffic from ports range")
require.NoError(t, err, "failed to add rule")
checkRuleSpecs(t, ipv4Client, true, rule2.(*Rule).specs...)
checkRuleSpecs(t, ipv4Client, ChainInputFilterName, true, rule2.(*Rule).specs...)
})
t.Run("delete first rule", func(t *testing.T) {
if err := manager.DeleteRule(rule1); err != nil {
t.Errorf("failed to delete rule: %v", err)
require.NoError(t, err, "failed to delete rule")
}
checkRuleSpecs(t, ipv4Client, false, rule1.(*Rule).specs...)
checkRuleSpecs(t, ipv4Client, ChainOutputFilterName, false, rule1.(*Rule).specs...)
})
t.Run("delete second rule", func(t *testing.T) {
if err := manager.DeleteRule(rule2); err != nil {
t.Errorf("failed to delete rule: %v", err)
require.NoError(t, err, "failed to delete rule")
}
checkRuleSpecs(t, ipv4Client, false, rule2.(*Rule).specs...)
checkRuleSpecs(t, ipv4Client, ChainInputFilterName, false, rule2.(*Rule).specs...)
})
t.Run("reset check", func(t *testing.T) {
// add second rule
ip := net.ParseIP("10.20.0.3")
port := &fw.Port{Proto: fw.PortProtocolUDP, Values: []int{5353}}
_, err = manager.AddFiltering(ip, port, fw.DirectionDst, fw.ActionAccept, "accept Fake DNS traffic")
if err != nil {
t.Errorf("failed to add rule: %v", err)
}
port := &fw.Port{Values: []int{5353}}
_, err = manager.AddFiltering(ip, "udp", nil, port, fw.RuleDirectionOUT, fw.ActionAccept, "accept Fake DNS traffic")
require.NoError(t, err, "failed to add rule")
if err := manager.Reset(); err != nil {
t.Errorf("failed to reset: %v", err)
}
err = manager.Reset()
require.NoError(t, err, "failed to reset")
ok, err := ipv4Client.ChainExists("filter", ChainFilterName)
if err != nil {
t.Errorf("failed to drop chain: %v", err)
}
ok, err := ipv4Client.ChainExists("filter", ChainInputFilterName)
require.NoError(t, err, "failed check chain exists")
if ok {
t.Errorf("chain '%v' still exists after Reset", ChainFilterName)
require.NoErrorf(t, err, "chain '%v' still exists after Reset", ChainInputFilterName)
}
})
}
func checkRuleSpecs(t *testing.T, ipv4Client *iptables.IPTables, mustExists bool, rulespec ...string) {
exists, err := ipv4Client.Exists("filter", ChainFilterName, rulespec...)
if err != nil {
t.Errorf("failed to check rule: %v", err)
return
}
func checkRuleSpecs(t *testing.T, ipv4Client *iptables.IPTables, chainName string, mustExists bool, rulespec ...string) {
exists, err := ipv4Client.Exists("filter", chainName, rulespec...)
require.NoError(t, err, "failed to check rule")
require.Falsef(t, !exists && mustExists, "rule '%v' does not exist", rulespec)
require.Falsef(t, exists && !mustExists, "rule '%v' exist", rulespec)
}
if !exists && mustExists {
t.Errorf("rule '%v' does not exist", rulespec)
return
}
if exists && !mustExists {
t.Errorf("rule '%v' exist", rulespec)
return
func TestIptablesCreatePerformance(t *testing.T) {
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
// just check on the local interface
manager, err := Create("lo")
require.NoError(t, err)
time.Sleep(time.Second)
defer func() {
if err := manager.Reset(); err != nil {
t.Errorf("clear the manager state: %v", err)
}
time.Sleep(time.Second)
}()
_, err = manager.client(net.ParseIP("10.20.0.100"))
require.NoError(t, err)
ip := net.ParseIP("10.20.0.100")
start := time.Now()
for i := 0; i < testMax; i++ {
port := &fw.Port{Values: []int{1000 + i}}
if i%2 == 0 {
_, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionOUT, fw.ActionAccept, "accept HTTP traffic")
} else {
_, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionIN, fw.ActionAccept, "accept HTTP traffic")
}
require.NoError(t, err, "failed to add rule")
}
t.Logf("execution avg per rule: %s", time.Since(start)/time.Duration(testMax))
})
}
}

View File

@ -4,6 +4,7 @@ package iptables
type Rule struct {
id string
specs []string
dst bool
v6 bool
}

View File

@ -0,0 +1,435 @@
package nftables
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"net/netip"
"strings"
"sync"
"github.com/google/nftables"
"github.com/google/nftables/expr"
"github.com/google/uuid"
"golang.org/x/sys/unix"
fw "github.com/netbirdio/netbird/client/firewall"
)
const (
// FilterTableName is the name of the table that is used for filtering by the Netbird client
FilterTableName = "netbird-acl"
// FilterInputChainName is the name of the chain that is used for filtering incoming packets
FilterInputChainName = "netbird-acl-input-filter"
// FilterOutputChainName is the name of the chain that is used for filtering outgoing packets
FilterOutputChainName = "netbird-acl-output-filter"
)
// Manager of iptables firewall
type Manager struct {
mutex sync.Mutex
conn *nftables.Conn
tableIPv4 *nftables.Table
tableIPv6 *nftables.Table
filterInputChainIPv4 *nftables.Chain
filterOutputChainIPv4 *nftables.Chain
filterInputChainIPv6 *nftables.Chain
filterOutputChainIPv6 *nftables.Chain
wgIfaceName string
}
// Create nftables firewall manager
func Create(wgIfaceName string) (*Manager, error) {
m := &Manager{
conn: &nftables.Conn{},
wgIfaceName: wgIfaceName,
}
if err := m.Reset(); err != nil {
return nil, err
}
return m, nil
}
// AddFiltering rule to the firewall
//
// If comment argument is empty firewall manager should set
// rule ID as comment for the rule
func (m *Manager) AddFiltering(
ip net.IP,
proto fw.Protocol,
sPort *fw.Port,
dPort *fw.Port,
direction fw.RuleDirection,
action fw.Action,
comment string,
) (fw.Rule, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
var (
err error
table *nftables.Table
chain *nftables.Chain
)
if direction == fw.RuleDirectionOUT {
table, chain, err = m.chain(
ip,
FilterOutputChainName,
nftables.ChainHookOutput,
nftables.ChainPriorityFilter,
nftables.ChainTypeFilter)
} else {
table, chain, err = m.chain(
ip,
FilterInputChainName,
nftables.ChainHookInput,
nftables.ChainPriorityFilter,
nftables.ChainTypeFilter)
}
if err != nil {
return nil, err
}
ifaceKey := expr.MetaKeyIIFNAME
if direction == fw.RuleDirectionOUT {
ifaceKey = expr.MetaKeyOIFNAME
}
expressions := []expr.Any{
&expr.Meta{Key: ifaceKey, Register: 1},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: ifname(m.wgIfaceName),
},
}
if proto != "all" {
expressions = append(expressions, &expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: uint32(9),
Len: uint32(1),
})
var protoData []byte
switch proto {
case fw.ProtocolTCP:
protoData = []byte{unix.IPPROTO_TCP}
case fw.ProtocolUDP:
protoData = []byte{unix.IPPROTO_UDP}
case fw.ProtocolICMP:
protoData = []byte{unix.IPPROTO_ICMP}
default:
return nil, fmt.Errorf("unsupported protocol: %s", proto)
}
expressions = append(expressions, &expr.Cmp{
Register: 1,
Op: expr.CmpOpEq,
Data: protoData,
})
}
// source address position
var adrLen, adrOffset uint32
if ip.To4() == nil {
adrLen = 16
adrOffset = 8
} else {
adrLen = 4
adrOffset = 12
}
// change to destination address position if need
if direction == fw.RuleDirectionOUT {
adrOffset += adrLen
}
ipToAdd, _ := netip.AddrFromSlice(ip)
add := ipToAdd.Unmap()
expressions = append(expressions,
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: adrOffset,
Len: adrLen,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: add.AsSlice(),
},
)
if sPort != nil && len(sPort.Values) != 0 {
expressions = append(expressions,
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseTransportHeader,
Offset: 0,
Len: 2,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: encodePort(*sPort),
},
)
}
if dPort != nil && len(dPort.Values) != 0 {
expressions = append(expressions,
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseTransportHeader,
Offset: 2,
Len: 2,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: encodePort(*dPort),
},
)
}
if action == fw.ActionAccept {
expressions = append(expressions, &expr.Verdict{Kind: expr.VerdictAccept})
} else {
expressions = append(expressions, &expr.Verdict{Kind: expr.VerdictDrop})
}
id := uuid.New().String()
userData := []byte(strings.Join([]string{id, comment}, " "))
_ = m.conn.InsertRule(&nftables.Rule{
Table: table,
Chain: chain,
Position: 0,
Exprs: expressions,
UserData: userData,
})
if err := m.conn.Flush(); err != nil {
return nil, err
}
list, err := m.conn.GetRules(table, chain)
if err != nil {
return nil, err
}
// Add the rule to the chain
rule := &Rule{id: id}
for _, r := range list {
if bytes.Equal(r.UserData, userData) {
rule.Rule = r
break
}
}
if rule.Rule == nil {
return nil, fmt.Errorf("rule not found")
}
return rule, nil
}
// chain returns the chain for the given IP address with specific settings
func (m *Manager) chain(
ip net.IP,
name string,
hook nftables.ChainHook,
priority nftables.ChainPriority,
cType nftables.ChainType,
) (*nftables.Table, *nftables.Chain, error) {
var err error
getChain := func(c *nftables.Chain, tf nftables.TableFamily) (*nftables.Chain, error) {
if c != nil {
return c, nil
}
return m.createChainIfNotExists(tf, name, hook, priority, cType)
}
if ip.To4() != nil {
if name == FilterInputChainName {
m.filterInputChainIPv4, err = getChain(m.filterInputChainIPv4, nftables.TableFamilyIPv4)
return m.tableIPv4, m.filterInputChainIPv4, err
}
m.filterOutputChainIPv4, err = getChain(m.filterOutputChainIPv4, nftables.TableFamilyIPv4)
return m.tableIPv4, m.filterOutputChainIPv4, err
}
if name == FilterInputChainName {
m.filterInputChainIPv6, err = getChain(m.filterInputChainIPv6, nftables.TableFamilyIPv6)
return m.tableIPv4, m.filterInputChainIPv6, err
}
m.filterOutputChainIPv6, err = getChain(m.filterOutputChainIPv6, nftables.TableFamilyIPv6)
return m.tableIPv4, m.filterOutputChainIPv6, err
}
// table returns the table for the given family of the IP address
func (m *Manager) table(family nftables.TableFamily) (*nftables.Table, error) {
if family == nftables.TableFamilyIPv4 {
if m.tableIPv4 != nil {
return m.tableIPv4, nil
}
table, err := m.createTableIfNotExists(nftables.TableFamilyIPv4)
if err != nil {
return nil, err
}
m.tableIPv4 = table
return m.tableIPv4, nil
}
if m.tableIPv6 != nil {
return m.tableIPv6, nil
}
table, err := m.createTableIfNotExists(nftables.TableFamilyIPv6)
if err != nil {
return nil, err
}
m.tableIPv6 = table
return m.tableIPv6, nil
}
func (m *Manager) createTableIfNotExists(family nftables.TableFamily) (*nftables.Table, error) {
tables, err := m.conn.ListTablesOfFamily(family)
if err != nil {
return nil, fmt.Errorf("list of tables: %w", err)
}
for _, t := range tables {
if t.Name == FilterTableName {
return t, nil
}
}
return m.conn.AddTable(&nftables.Table{Name: FilterTableName, Family: nftables.TableFamilyIPv4}), nil
}
func (m *Manager) createChainIfNotExists(
family nftables.TableFamily,
name string,
hooknum nftables.ChainHook,
priority nftables.ChainPriority,
chainType nftables.ChainType,
) (*nftables.Chain, error) {
table, err := m.table(family)
if err != nil {
return nil, err
}
chains, err := m.conn.ListChainsOfTableFamily(family)
if err != nil {
return nil, fmt.Errorf("list of chains: %w", err)
}
for _, c := range chains {
if c.Name == name && c.Table.Name == table.Name {
return c, nil
}
}
polAccept := nftables.ChainPolicyAccept
chain := &nftables.Chain{
Name: name,
Table: table,
Hooknum: hooknum,
Priority: priority,
Type: chainType,
Policy: &polAccept,
}
chain = m.conn.AddChain(chain)
ifaceKey := expr.MetaKeyIIFNAME
if name == FilterOutputChainName {
ifaceKey = expr.MetaKeyOIFNAME
}
expressions := []expr.Any{
&expr.Meta{Key: ifaceKey, Register: 1},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: ifname(m.wgIfaceName),
},
&expr.Verdict{Kind: expr.VerdictDrop},
}
_ = m.conn.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: expressions,
})
if err := m.conn.Flush(); err != nil {
return nil, err
}
return chain, nil
}
// DeleteRule from the firewall by rule definition
func (m *Manager) DeleteRule(rule fw.Rule) error {
nativeRule, ok := rule.(*Rule)
if !ok {
return fmt.Errorf("invalid rule type")
}
if err := m.conn.DelRule(nativeRule.Rule); err != nil {
return err
}
return m.conn.Flush()
}
// Reset firewall to the default state
func (m *Manager) Reset() error {
m.mutex.Lock()
defer m.mutex.Unlock()
chains, err := m.conn.ListChains()
if err != nil {
return fmt.Errorf("list of chains: %w", err)
}
for _, c := range chains {
if c.Name == FilterInputChainName || c.Name == FilterOutputChainName {
m.conn.DelChain(c)
}
}
tables, err := m.conn.ListTables()
if err != nil {
return fmt.Errorf("list of tables: %w", err)
}
for _, t := range tables {
if t.Name == FilterTableName {
m.conn.DelTable(t)
}
}
return m.conn.Flush()
}
func encodePort(port fw.Port) []byte {
bs := make([]byte, 2)
binary.BigEndian.PutUint16(bs, uint16(port.Values[0]))
return bs
}
func ifname(n string) []byte {
b := make([]byte, 16)
copy(b, []byte(n+"\x00"))
return b
}

View File

@ -0,0 +1,137 @@
package nftables
import (
"fmt"
"net"
"net/netip"
"testing"
"time"
"github.com/google/nftables"
"github.com/google/nftables/expr"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
fw "github.com/netbirdio/netbird/client/firewall"
)
func TestNftablesManager(t *testing.T) {
// just check on the local interface
manager, err := Create("lo")
require.NoError(t, err)
time.Sleep(time.Second)
defer func() {
err = manager.Reset()
require.NoError(t, err, "failed to reset")
time.Sleep(time.Second)
}()
ip := net.ParseIP("100.96.0.1")
testClient := &nftables.Conn{}
rule, err := manager.AddFiltering(
ip,
fw.ProtocolTCP,
nil,
&fw.Port{Values: []int{53}},
fw.RuleDirectionIN,
fw.ActionDrop,
"",
)
require.NoError(t, err, "failed to add rule")
rules, err := testClient.GetRules(manager.tableIPv4, manager.filterInputChainIPv4)
require.NoError(t, err, "failed to get rules")
// 1 regular rule and other "drop all rule" for the interface
require.Len(t, rules, 2, "expected 1 rule")
ipToAdd, _ := netip.AddrFromSlice(ip)
add := ipToAdd.Unmap()
expectedExprs := []expr.Any{
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: ifname("lo"),
},
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: uint32(9),
Len: uint32(1),
},
&expr.Cmp{
Register: 1,
Op: expr.CmpOpEq,
Data: []byte{unix.IPPROTO_TCP},
},
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: 12,
Len: 4,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: add.AsSlice(),
},
&expr.Payload{
DestRegister: 1,
Base: expr.PayloadBaseTransportHeader,
Offset: 2,
Len: 2,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{0, 53},
},
&expr.Verdict{Kind: expr.VerdictDrop},
}
require.ElementsMatch(t, rules[0].Exprs, expectedExprs, "expected the same expressions")
err = manager.DeleteRule(rule)
require.NoError(t, err, "failed to delete rule")
rules, err = testClient.GetRules(manager.tableIPv4, manager.filterInputChainIPv4)
require.NoError(t, err, "failed to get rules")
require.Len(t, rules, 1, "expected 1 rules after deleteion")
err = manager.Reset()
require.NoError(t, err, "failed to reset")
}
func TestNFtablesCreatePerformance(t *testing.T) {
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
// just check on the local interface
manager, err := Create("lo")
require.NoError(t, err)
time.Sleep(time.Second)
defer func() {
if err := manager.Reset(); err != nil {
t.Errorf("clear the manager state: %v", err)
}
time.Sleep(time.Second)
}()
ip := net.ParseIP("10.20.0.100")
start := time.Now()
for i := 0; i < testMax; i++ {
port := &fw.Port{Values: []int{1000 + i}}
if i%2 == 0 {
_, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionOUT, fw.ActionAccept, "accept HTTP traffic")
} else {
_, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionIN, fw.ActionAccept, "accept HTTP traffic")
}
require.NoError(t, err, "failed to add rule")
}
t.Logf("execution avg per rule: %s", time.Since(start)/time.Duration(testMax))
})
}
}

View File

@ -0,0 +1,16 @@
package nftables
import (
"github.com/google/nftables"
)
// Rule to handle management of rules
type Rule struct {
*nftables.Rule
id string
}
// GetRuleID returns the rule id
func (r *Rule) GetRuleID() string {
return r.id
}

View File

@ -1,14 +1,23 @@
package firewall
// PortProtocol is the protocol of the port
type PortProtocol string
// Protocol is the protocol of the port
type Protocol string
const (
// PortProtocolTCP is the TCP protocol
PortProtocolTCP PortProtocol = "tcp"
// ProtocolTCP is the TCP protocol
ProtocolTCP Protocol = "tcp"
// PortProtocolUDP is the UDP protocol
PortProtocolUDP PortProtocol = "udp"
// ProtocolUDP is the UDP protocol
ProtocolUDP Protocol = "udp"
// ProtocolICMP is the ICMP protocol
ProtocolICMP Protocol = "icmp"
// ProtocolALL cover all supported protocols
ProtocolALL Protocol = "all"
// ProtocolUnknown unknown protocol
ProtocolUnknown Protocol = "unknown"
)
// Port of the address for firewall rule
@ -18,7 +27,4 @@ type Port struct {
// Values contains one value for single port, multiple values for the list of ports, or two values for the range of ports
Values []int
// Proto is the protocol of the port
Proto PortProtocol
}

View File

@ -0,0 +1,27 @@
package uspfilter
import (
"net"
"github.com/google/gopacket"
fw "github.com/netbirdio/netbird/client/firewall"
)
// Rule to handle management of rules
type Rule struct {
id string
ip net.IP
ipLayer gopacket.LayerType
protoLayer gopacket.LayerType
direction fw.RuleDirection
sPort uint16
dPort uint16
drop bool
comment string
}
// GetRuleID returns the rule id
func (r *Rule) GetRuleID() string {
return r.id
}

View File

@ -0,0 +1,291 @@
package uspfilter
import (
"fmt"
"net"
"sync"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
fw "github.com/netbirdio/netbird/client/firewall"
"github.com/netbirdio/netbird/iface"
)
const layerTypeAll = 0
// IFaceMapper defines subset methods of interface required for manager
type IFaceMapper interface {
SetFiltering(iface.PacketFilter) error
}
// Manager userspace firewall manager
type Manager struct {
outgoingRules []Rule
incomingRules []Rule
rulesIndex map[string]int
wgNetwork *net.IPNet
decoders sync.Pool
mutex sync.RWMutex
}
// decoder for packages
type decoder struct {
eth layers.Ethernet
ip4 layers.IPv4
ip6 layers.IPv6
tcp layers.TCP
udp layers.UDP
icmp4 layers.ICMPv4
icmp6 layers.ICMPv6
decoded []gopacket.LayerType
parser *gopacket.DecodingLayerParser
}
// Create userspace firewall manager constructor
func Create(iface IFaceMapper) (*Manager, error) {
m := &Manager{
rulesIndex: make(map[string]int),
decoders: sync.Pool{
New: func() any {
d := &decoder{
decoded: []gopacket.LayerType{},
}
d.parser = gopacket.NewDecodingLayerParser(
layers.LayerTypeIPv4,
&d.eth, &d.ip4, &d.ip6, &d.icmp4, &d.icmp6, &d.tcp, &d.udp,
)
d.parser.IgnoreUnsupported = true
return d
},
},
}
if err := iface.SetFiltering(m); err != nil {
return nil, err
}
return m, nil
}
// AddFiltering rule to the firewall
//
// If comment argument is empty firewall manager should set
// rule ID as comment for the rule
func (m *Manager) AddFiltering(
ip net.IP,
proto fw.Protocol,
sPort *fw.Port,
dPort *fw.Port,
direction fw.RuleDirection,
action fw.Action,
comment string,
) (fw.Rule, error) {
r := Rule{
id: uuid.New().String(),
ip: ip,
ipLayer: layers.LayerTypeIPv6,
direction: direction,
drop: action == fw.ActionDrop,
comment: comment,
}
if ipNormalized := ip.To4(); ipNormalized != nil {
r.ipLayer = layers.LayerTypeIPv4
r.ip = ipNormalized
}
if sPort != nil && len(sPort.Values) == 1 {
r.sPort = uint16(sPort.Values[0])
}
if dPort != nil && len(dPort.Values) == 1 {
r.dPort = uint16(dPort.Values[0])
}
switch proto {
case fw.ProtocolTCP:
r.protoLayer = layers.LayerTypeTCP
case fw.ProtocolUDP:
r.protoLayer = layers.LayerTypeUDP
case fw.ProtocolICMP:
r.protoLayer = layers.LayerTypeICMPv4
if r.ipLayer == layers.LayerTypeIPv6 {
r.protoLayer = layers.LayerTypeICMPv6
}
case fw.ProtocolALL:
r.protoLayer = layerTypeAll
}
m.mutex.Lock()
var p int
if direction == fw.RuleDirectionIN {
m.incomingRules = append(m.incomingRules, r)
p = len(m.incomingRules) - 1
} else {
m.outgoingRules = append(m.outgoingRules, r)
p = len(m.outgoingRules) - 1
}
m.rulesIndex[r.id] = p
m.mutex.Unlock()
return &r, nil
}
// DeleteRule from the firewall by rule definition
func (m *Manager) DeleteRule(rule fw.Rule) error {
m.mutex.Lock()
defer m.mutex.Unlock()
r, ok := rule.(*Rule)
if !ok {
return fmt.Errorf("delete rule: invalid rule type: %T", rule)
}
p, ok := m.rulesIndex[r.id]
if !ok {
return fmt.Errorf("delete rule: no rule with such id: %v", r.id)
}
delete(m.rulesIndex, r.id)
var toUpdate []Rule
if r.direction == fw.RuleDirectionIN {
m.incomingRules = append(m.incomingRules[:p], m.incomingRules[p+1:]...)
toUpdate = m.incomingRules
} else {
m.outgoingRules = append(m.outgoingRules[:p], m.outgoingRules[p+1:]...)
toUpdate = m.outgoingRules
}
for i := 0; i < len(toUpdate); i++ {
m.rulesIndex[toUpdate[i].id] = i
}
return nil
}
// Reset firewall to the default state
func (m *Manager) Reset() error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.outgoingRules = m.outgoingRules[:0]
m.incomingRules = m.incomingRules[:0]
m.rulesIndex = make(map[string]int)
return nil
}
// DropOutgoing filter outgoing packets
func (m *Manager) DropOutgoing(packetData []byte) bool {
return m.dropFilter(packetData, m.outgoingRules, false)
}
// DropIncoming filter incoming packets
func (m *Manager) DropIncoming(packetData []byte) bool {
return m.dropFilter(packetData, m.incomingRules, true)
}
// dropFilter imlements same logic for booth direction of the traffic
func (m *Manager) dropFilter(packetData []byte, rules []Rule, isIncomingPacket bool) bool {
m.mutex.RLock()
defer m.mutex.RUnlock()
d := m.decoders.Get().(*decoder)
defer m.decoders.Put(d)
if err := d.parser.DecodeLayers(packetData, &d.decoded); err != nil {
log.Tracef("couldn't decode layer, err: %s", err)
return true
}
if len(d.decoded) < 2 {
log.Tracef("not enough levels in network packet")
return true
}
ipLayer := d.decoded[0]
switch ipLayer {
case layers.LayerTypeIPv4:
if !m.wgNetwork.Contains(d.ip4.SrcIP) || !m.wgNetwork.Contains(d.ip4.DstIP) {
return false
}
case layers.LayerTypeIPv6:
if !m.wgNetwork.Contains(d.ip6.SrcIP) || !m.wgNetwork.Contains(d.ip6.DstIP) {
return false
}
default:
log.Errorf("unknown layer: %v", d.decoded[0])
return true
}
payloadLayer := d.decoded[1]
// check if IP address match by IP
for _, rule := range rules {
switch ipLayer {
case layers.LayerTypeIPv4:
if isIncomingPacket {
if !d.ip4.SrcIP.Equal(rule.ip) {
continue
}
} else {
if !d.ip4.DstIP.Equal(rule.ip) {
continue
}
}
case layers.LayerTypeIPv6:
if isIncomingPacket {
if !d.ip6.SrcIP.Equal(rule.ip) {
continue
}
} else {
if !d.ip6.DstIP.Equal(rule.ip) {
continue
}
}
}
if rule.protoLayer == layerTypeAll {
return rule.drop
}
if payloadLayer != rule.protoLayer {
continue
}
switch payloadLayer {
case layers.LayerTypeTCP:
if rule.sPort == 0 && rule.dPort == 0 {
return rule.drop
}
if rule.sPort != 0 && rule.sPort == uint16(d.tcp.SrcPort) {
return rule.drop
}
if rule.dPort != 0 && rule.dPort == uint16(d.tcp.DstPort) {
return rule.drop
}
case layers.LayerTypeUDP:
if rule.sPort == 0 && rule.dPort == 0 {
return rule.drop
}
if rule.sPort != 0 && rule.sPort == uint16(d.udp.SrcPort) {
return rule.drop
}
if rule.dPort != 0 && rule.dPort == uint16(d.udp.DstPort) {
return rule.drop
}
return rule.drop
case layers.LayerTypeICMPv4, layers.LayerTypeICMPv6:
return rule.drop
}
}
// default policy is DROP ALL
return true
}
// SetNetwork of the wireguard interface to which filtering applied
func (m *Manager) SetNetwork(network *net.IPNet) {
m.wgNetwork = network
}

View File

@ -0,0 +1,207 @@
package uspfilter
import (
"fmt"
"net"
"testing"
"time"
"github.com/stretchr/testify/require"
fw "github.com/netbirdio/netbird/client/firewall"
"github.com/netbirdio/netbird/iface"
)
type IFaceMock struct {
SetFilteringFunc func(iface.PacketFilter) error
}
func (i *IFaceMock) SetFiltering(iface iface.PacketFilter) error {
if i.SetFilteringFunc == nil {
return fmt.Errorf("not implemented")
}
return i.SetFilteringFunc(iface)
}
func TestManagerCreate(t *testing.T) {
ifaceMock := &IFaceMock{
SetFilteringFunc: func(iface.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock)
if err != nil {
t.Errorf("failed to create Manager: %v", err)
return
}
if m == nil {
t.Error("Manager is nil")
}
}
func TestManagerAddFiltering(t *testing.T) {
isSetFilteringCalled := false
ifaceMock := &IFaceMock{
SetFilteringFunc: func(iface.PacketFilter) error {
isSetFilteringCalled = true
return nil
},
}
m, err := Create(ifaceMock)
if err != nil {
t.Errorf("failed to create Manager: %v", err)
return
}
ip := net.ParseIP("192.168.1.1")
proto := fw.ProtocolTCP
port := &fw.Port{Values: []int{80}}
direction := fw.RuleDirectionOUT
action := fw.ActionDrop
comment := "Test rule"
rule, err := m.AddFiltering(ip, proto, nil, port, direction, action, comment)
if err != nil {
t.Errorf("failed to add filtering: %v", err)
return
}
if rule == nil {
t.Error("Rule is nil")
return
}
if !isSetFilteringCalled {
t.Error("SetFiltering was not called")
return
}
}
func TestManagerDeleteRule(t *testing.T) {
ifaceMock := &IFaceMock{
SetFilteringFunc: func(iface.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock)
if err != nil {
t.Errorf("failed to create Manager: %v", err)
return
}
ip := net.ParseIP("192.168.1.1")
proto := fw.ProtocolTCP
port := &fw.Port{Values: []int{80}}
direction := fw.RuleDirectionOUT
action := fw.ActionDrop
comment := "Test rule"
rule, err := m.AddFiltering(ip, proto, nil, port, direction, action, comment)
if err != nil {
t.Errorf("failed to add filtering: %v", err)
return
}
ip = net.ParseIP("192.168.1.1")
proto = fw.ProtocolTCP
port = &fw.Port{Values: []int{80}}
direction = fw.RuleDirectionIN
action = fw.ActionDrop
comment = "Test rule 2"
rule2, err := m.AddFiltering(ip, proto, nil, port, direction, action, comment)
if err != nil {
t.Errorf("failed to add filtering: %v", err)
return
}
err = m.DeleteRule(rule)
if err != nil {
t.Errorf("failed to delete rule: %v", err)
return
}
if idx, ok := m.rulesIndex[rule2.GetRuleID()]; !ok || len(m.incomingRules) != 1 || idx != 0 {
t.Errorf("rule2 is not in the rulesIndex")
}
err = m.DeleteRule(rule2)
if err != nil {
t.Errorf("failed to delete rule: %v", err)
return
}
if len(m.rulesIndex) != 0 || len(m.incomingRules) != 0 {
t.Errorf("rule1 still in the rulesIndex")
}
}
func TestManagerReset(t *testing.T) {
ifaceMock := &IFaceMock{
SetFilteringFunc: func(iface.PacketFilter) error { return nil },
}
m, err := Create(ifaceMock)
if err != nil {
t.Errorf("failed to create Manager: %v", err)
return
}
ip := net.ParseIP("192.168.1.1")
proto := fw.ProtocolTCP
port := &fw.Port{Values: []int{80}}
direction := fw.RuleDirectionOUT
action := fw.ActionDrop
comment := "Test rule"
_, err = m.AddFiltering(ip, proto, nil, port, direction, action, comment)
if err != nil {
t.Errorf("failed to add filtering: %v", err)
return
}
err = m.Reset()
if err != nil {
t.Errorf("failed to reset Manager: %v", err)
return
}
if len(m.rulesIndex) != 0 || len(m.outgoingRules) != 0 || len(m.incomingRules) != 0 {
t.Errorf("rules is not empty")
}
}
func TestUSPFilterCreatePerformance(t *testing.T) {
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
// just check on the local interface
ifaceMock := &IFaceMock{
SetFilteringFunc: func(iface.PacketFilter) error { return nil },
}
manager, err := Create(ifaceMock)
require.NoError(t, err)
time.Sleep(time.Second)
defer func() {
if err := manager.Reset(); err != nil {
t.Errorf("clear the manager state: %v", err)
}
time.Sleep(time.Second)
}()
ip := net.ParseIP("10.20.0.100")
start := time.Now()
for i := 0; i < testMax; i++ {
port := &fw.Port{Values: []int{1000 + i}}
if i%2 == 0 {
_, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionOUT, fw.ActionAccept, "accept HTTP traffic")
} else {
_, err = manager.AddFiltering(ip, "tcp", nil, port, fw.RuleDirectionIN, fw.ActionAccept, "accept HTTP traffic")
}
require.NoError(t, err, "failed to add rule")
}
t.Logf("execution avg per rule: %s", time.Since(start)/time.Duration(testMax))
})
}
}

View File

@ -0,0 +1,209 @@
package acl
import (
"fmt"
"net"
"strconv"
"sync"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/firewall"
"github.com/netbirdio/netbird/iface"
mgmProto "github.com/netbirdio/netbird/management/proto"
)
// iFaceMapper defines subset methods of interface required for manager
type iFaceMapper interface {
Name() string
IsUserspaceBind() bool
SetFiltering(iface.PacketFilter) error
}
// Manager is a ACL rules manager
type Manager interface {
ApplyFiltering(rules []*mgmProto.FirewallRule)
Stop()
}
// DefaultManager uses firewall manager to handle
type DefaultManager struct {
manager firewall.Manager
rulesPairs map[string][]firewall.Rule
mutex sync.Mutex
}
// ApplyFiltering firewall rules to the local firewall manager processed by ACL policy.
func (d *DefaultManager) ApplyFiltering(rules []*mgmProto.FirewallRule) {
d.mutex.Lock()
defer d.mutex.Unlock()
if d.manager == nil {
log.Debug("firewall manager is not supported, skipping firewall rules")
return
}
var (
applyFailed bool
newRulePairs = make(map[string][]firewall.Rule)
)
for _, r := range rules {
rules, err := d.protoRuleToFirewallRule(r)
if err != nil {
log.Errorf("failed to apply firewall rule: %+v, %v", r, err)
applyFailed = true
break
}
newRulePairs[rules[0].GetRuleID()] = rules
}
if applyFailed {
log.Error("failed to apply firewall rules, rollback ACL to previous state")
for _, rules := range newRulePairs {
for _, rule := range rules {
if err := d.manager.DeleteRule(rule); err != nil {
log.Errorf("failed to delete new firewall rule (id: %v) during rollback: %v", rule.GetRuleID(), err)
continue
}
}
}
return
}
for pairID, rules := range d.rulesPairs {
if _, ok := newRulePairs[pairID]; !ok {
for _, rule := range rules {
if err := d.manager.DeleteRule(rule); err != nil {
log.Errorf("failed to delete firewall rule: %v", err)
continue
}
}
delete(d.rulesPairs, pairID)
}
}
d.rulesPairs = newRulePairs
}
// Stop ACL controller and clear firewall state
func (d *DefaultManager) Stop() {
d.mutex.Lock()
defer d.mutex.Unlock()
if err := d.manager.Reset(); err != nil {
log.WithError(err).Error("reset firewall state")
}
}
func (d *DefaultManager) protoRuleToFirewallRule(r *mgmProto.FirewallRule) ([]firewall.Rule, error) {
ip := net.ParseIP(r.PeerIP)
if ip == nil {
return nil, fmt.Errorf("invalid IP address, skipping firewall rule")
}
protocol := convertToFirewallProtocol(r.Protocol)
if protocol == firewall.ProtocolUnknown {
return nil, fmt.Errorf("invalid protocol type: %d, skipping firewall rule", r.Protocol)
}
action := convertFirewallAction(r.Action)
if action == firewall.ActionUnknown {
return nil, fmt.Errorf("invalid action type: %d, skipping firewall rule", r.Action)
}
var port *firewall.Port
if r.Port != "" {
value, err := strconv.Atoi(r.Port)
if err != nil {
return nil, fmt.Errorf("invalid port, skipping firewall rule")
}
port = &firewall.Port{
Values: []int{value},
}
}
var rules []firewall.Rule
var err error
switch r.Direction {
case mgmProto.FirewallRule_IN:
rules, err = d.addInRules(ip, protocol, port, action, "")
case mgmProto.FirewallRule_OUT:
rules, err = d.addOutRules(ip, protocol, port, action, "")
default:
return nil, fmt.Errorf("invalid direction, skipping firewall rule")
}
if err != nil {
return nil, err
}
d.rulesPairs[rules[0].GetRuleID()] = rules
return rules, nil
}
func (d *DefaultManager) addInRules(ip net.IP, protocol firewall.Protocol, port *firewall.Port, action firewall.Action, comment string) ([]firewall.Rule, error) {
var rules []firewall.Rule
rule, err := d.manager.AddFiltering(ip, protocol, nil, port, firewall.RuleDirectionIN, action, comment)
if err != nil {
return nil, fmt.Errorf("failed to add firewall rule: %v", err)
}
rules = append(rules, rule)
if shouldSkipInvertedRule(protocol) {
return rules, nil
}
rule, err = d.manager.AddFiltering(ip, protocol, port, nil, firewall.RuleDirectionOUT, action, comment)
if err != nil {
return nil, fmt.Errorf("failed to add firewall rule: %v", err)
}
return append(rules, rule), nil
}
func (d *DefaultManager) addOutRules(ip net.IP, protocol firewall.Protocol, port *firewall.Port, action firewall.Action, comment string) ([]firewall.Rule, error) {
var rules []firewall.Rule
rule, err := d.manager.AddFiltering(ip, protocol, nil, port, firewall.RuleDirectionOUT, action, comment)
if err != nil {
return nil, fmt.Errorf("failed to add firewall rule: %v", err)
}
rules = append(rules, rule)
if shouldSkipInvertedRule(protocol) {
return rules, nil
}
rule, err = d.manager.AddFiltering(ip, protocol, port, nil, firewall.RuleDirectionIN, action, comment)
if err != nil {
return nil, fmt.Errorf("failed to add firewall rule: %v", err)
}
return append(rules, rule), nil
}
func convertToFirewallProtocol(protocol mgmProto.FirewallRuleProtocol) firewall.Protocol {
switch protocol {
case mgmProto.FirewallRule_TCP:
return firewall.ProtocolTCP
case mgmProto.FirewallRule_UDP:
return firewall.ProtocolUDP
case mgmProto.FirewallRule_ICMP:
return firewall.ProtocolICMP
case mgmProto.FirewallRule_ALL:
return firewall.ProtocolALL
default:
return firewall.ProtocolUnknown
}
}
func shouldSkipInvertedRule(protocol firewall.Protocol) bool {
return protocol == firewall.ProtocolALL || protocol == firewall.ProtocolICMP
}
func convertFirewallAction(action mgmProto.FirewallRuleAction) firewall.Action {
switch action {
case mgmProto.FirewallRule_ACCEPT:
return firewall.ActionAccept
case mgmProto.FirewallRule_DROP:
return firewall.ActionDrop
default:
return firewall.ActionUnknown
}
}

View File

@ -0,0 +1,27 @@
//go:build !linux
package acl
import (
"fmt"
"runtime"
"github.com/netbirdio/netbird/client/firewall"
"github.com/netbirdio/netbird/client/firewall/uspfilter"
)
// Create creates a firewall manager instance
func Create(iface iFaceMapper) (manager *DefaultManager, err error) {
if iface.IsUserspaceBind() {
// use userspace packet filtering firewall
fm, err := uspfilter.Create(iface)
if err != nil {
return nil, err
}
return &DefaultManager{
manager: fm,
rulesPairs: make(map[string][]firewall.Rule),
}, nil
}
return nil, fmt.Errorf("not implemented for this OS: %s", runtime.GOOS)
}

View File

@ -0,0 +1,36 @@
package acl
import (
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/firewall"
"github.com/netbirdio/netbird/client/firewall/iptables"
"github.com/netbirdio/netbird/client/firewall/nftables"
"github.com/netbirdio/netbird/client/firewall/uspfilter"
)
// Create creates a firewall manager instance for the Linux
func Create(iface iFaceMapper) (manager *DefaultManager, err error) {
var fm firewall.Manager
if iface.IsUserspaceBind() {
// use userspace packet filtering firewall
if fm, err = uspfilter.Create(iface); err != nil {
log.Debugf("failed to create userspace filtering firewall: %s", err)
return nil, err
}
} else {
if fm, err = iptables.Create(iface.Name()); err != nil {
log.Debugf("failed to create iptables manager: %s", err)
// fallback to nftables
if fm, err = nftables.Create(iface.Name()); err != nil {
log.Errorf("failed to create nftables manager: %s", err)
return nil, err
}
}
}
return &DefaultManager{
manager: fm,
rulesPairs: make(map[string][]firewall.Rule),
}, nil
}

View File

@ -0,0 +1,92 @@
package acl
import (
"runtime"
"testing"
"github.com/golang/mock/gomock"
"github.com/netbirdio/netbird/client/internal/acl/mocks"
mgmProto "github.com/netbirdio/netbird/management/proto"
)
func TestDefaultManager(t *testing.T) {
// TODO: enable when other platform will be added
if runtime.GOOS != "linux" {
t.Skipf("ACL manager not supported in the: %s", runtime.GOOS)
return
}
fwRules := []*mgmProto.FirewallRule{
{
PeerIP: "10.93.0.1",
Direction: mgmProto.FirewallRule_OUT,
Action: mgmProto.FirewallRule_ACCEPT,
Protocol: mgmProto.FirewallRule_TCP,
Port: "80",
},
{
PeerIP: "10.93.0.2",
Direction: mgmProto.FirewallRule_OUT,
Action: mgmProto.FirewallRule_DROP,
Protocol: mgmProto.FirewallRule_UDP,
Port: "53",
},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
iface := mocks.NewMockIFaceMapper(ctrl)
iface.EXPECT().IsUserspaceBind().Return(false)
iface.EXPECT().Name().Return("lo")
// we receive one rule from the management so for testing purposes ignore it
acl, err := Create(iface)
if err != nil {
t.Errorf("create ACL manager: %v", err)
return
}
defer acl.Stop()
t.Run("apply firewall rules", func(t *testing.T) {
acl.ApplyFiltering(fwRules)
if len(acl.rulesPairs) != 2 {
t.Errorf("firewall rules not applied: %v", acl.rulesPairs)
return
}
})
t.Run("add extra rules", func(t *testing.T) {
// remove first rule
fwRules = fwRules[1:]
fwRules = append(fwRules, &mgmProto.FirewallRule{
PeerIP: "10.93.0.3",
Direction: mgmProto.FirewallRule_IN,
Action: mgmProto.FirewallRule_DROP,
Protocol: mgmProto.FirewallRule_ICMP,
})
existedRulesID := map[string]struct{}{}
for id := range acl.rulesPairs {
existedRulesID[id] = struct{}{}
}
acl.ApplyFiltering(fwRules)
// we should have one old and one new rule in the existed rules
if len(acl.rulesPairs) != 2 {
t.Errorf("firewall rules not applied")
return
}
// check that old rules was removed
for id := range existedRulesID {
if _, ok := acl.rulesPairs[id]; ok {
t.Errorf("old rule was not removed")
return
}
}
})
}

View File

@ -0,0 +1,77 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/netbirdio/netbird/client/internal/acl (interfaces: IFaceMapper)
// Package mocks is a generated GoMock package.
package mocks
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
iface "github.com/netbirdio/netbird/iface"
)
// MockIFaceMapper is a mock of IFaceMapper interface.
type MockIFaceMapper struct {
ctrl *gomock.Controller
recorder *MockIFaceMapperMockRecorder
}
// MockIFaceMapperMockRecorder is the mock recorder for MockIFaceMapper.
type MockIFaceMapperMockRecorder struct {
mock *MockIFaceMapper
}
// NewMockIFaceMapper creates a new mock instance.
func NewMockIFaceMapper(ctrl *gomock.Controller) *MockIFaceMapper {
mock := &MockIFaceMapper{ctrl: ctrl}
mock.recorder = &MockIFaceMapperMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIFaceMapper) EXPECT() *MockIFaceMapperMockRecorder {
return m.recorder
}
// IsUserspaceBind mocks base method.
func (m *MockIFaceMapper) IsUserspaceBind() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsUserspaceBind")
ret0, _ := ret[0].(bool)
return ret0
}
// IsUserspaceBind indicates an expected call of IsUserspaceBind.
func (mr *MockIFaceMapperMockRecorder) IsUserspaceBind() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUserspaceBind", reflect.TypeOf((*MockIFaceMapper)(nil).IsUserspaceBind))
}
// Name mocks base method.
func (m *MockIFaceMapper) Name() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Name")
ret0, _ := ret[0].(string)
return ret0
}
// Name indicates an expected call of Name.
func (mr *MockIFaceMapperMockRecorder) Name() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockIFaceMapper)(nil).Name))
}
// SetFiltering mocks base method.
func (m *MockIFaceMapper) SetFiltering(arg0 iface.PacketFilter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetFiltering", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetFiltering indicates an expected call of SetFiltering.
func (mr *MockIFaceMapperMockRecorder) SetFiltering(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFiltering", reflect.TypeOf((*MockIFaceMapper)(nil).SetFiltering), arg0)
}

View File

@ -17,6 +17,7 @@ import (
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"github.com/netbirdio/netbird/client/internal/acl"
"github.com/netbirdio/netbird/client/internal/dns"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/proxy"
@ -114,6 +115,7 @@ type Engine struct {
statusRecorder *peer.Status
routeManager routemanager.Manager
acl acl.Manager
dnsServer dns.Server
}
@ -222,6 +224,12 @@ func (e *Engine) Start() error {
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder)
if acl, err := acl.Create(e.wgInterface); err != nil {
log.Errorf("failed to create ACL manager, policy will not work: %s", err.Error())
} else {
e.acl = acl
}
if e.dnsServer == nil {
// todo fix custom address
dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress)
@ -622,6 +630,9 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
log.Errorf("failed to update dns server, err: %v", err)
}
if e.acl != nil {
e.acl.ApplyFiltering(networkMap.FirewallRules)
}
e.networkSerial = serial
return nil
}
@ -1005,6 +1016,9 @@ func (e *Engine) close() {
e.dnsServer.Stop()
}
if e.acl != nil {
e.acl.Stop()
}
}
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {

View File

@ -14,9 +14,6 @@ import (
"time"
"github.com/pion/transport/v2/stdnet"
"github.com/netbirdio/netbird/iface/bind"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -31,6 +28,7 @@ import (
"github.com/netbirdio/netbird/client/system"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/iface/bind"
mgmt "github.com/netbirdio/netbird/management/client"
mgmtProto "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server"

14
go.mod
View File

@ -36,6 +36,7 @@ require (
github.com/getlantern/systray v1.2.1
github.com/gliderlabs/ssh v0.3.4
github.com/godbus/dbus/v5 v5.1.0
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.9
github.com/google/gopacket v1.1.19
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
@ -48,7 +49,6 @@ require (
github.com/miekg/dns v1.1.43
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/okta/okta-sdk-golang/v2 v2.18.0
github.com/open-policy-agent/opa v0.49.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pion/logging v0.2.2
github.com/pion/stun v0.4.0
@ -71,14 +71,13 @@ require (
require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
@ -88,14 +87,12 @@ require (
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@ -116,16 +113,11 @@ require (
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.1.0 // indirect
github.com/yuin/goldmark v1.4.13 // indirect
go.opentelemetry.io/otel/sdk v1.11.1 // indirect
go.opentelemetry.io/otel/trace v1.11.1 // indirect
@ -133,10 +125,12 @@ require (
golang.org/x/mod v0.8.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/tools v0.6.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect

40
go.sum
View File

@ -53,16 +53,12 @@ github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpz
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10=
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -73,8 +69,6 @@ github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9Irpr
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/bazelbuild/rules_go v0.30.0/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M=
@ -84,7 +78,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU=
github.com/c-robinson/iplib v1.0.3/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
github.com/cenkalti/backoff v1.1.1-0.20190506075156-2146c9339422/go.mod h1:b6Nc7NRH5C4aCISLry0tLnTjcuTEvoiqcWDdsU0sOGM=
@ -92,7 +85,6 @@ github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@ -149,14 +141,13 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
@ -178,7 +169,6 @@ github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
@ -201,7 +191,6 @@ github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
@ -246,8 +235,6 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
@ -257,7 +244,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
@ -270,7 +256,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -279,6 +264,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -298,12 +284,10 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -412,7 +396,6 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -519,8 +502,6 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/open-policy-agent/opa v0.49.0 h1:TIlpCT1B5FSm8Dqo/a4t23gKmHkQysC3+7W77F99P4k=
github.com/open-policy-agent/opa v0.49.0/go.mod h1:WTLWtu498/QNTDkiHx76Xj7jaJUPvLJAPtdMkCcst0w=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
@ -594,8 +575,6 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -657,8 +636,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
@ -672,14 +649,8 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg=
github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -696,7 +667,6 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
@ -954,6 +924,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -986,6 +957,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1034,6 +1006,7 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
@ -1153,6 +1126,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=

90
iface/device_wrapper.go Normal file
View File

@ -0,0 +1,90 @@
package iface
import (
"net"
"sync"
"golang.zx2c4.com/wireguard/tun"
)
// PacketFilter interface for firewall abilities
type PacketFilter interface {
// DropOutgoing filter outgoing packets from host to external destinations
DropOutgoing(packetData []byte) bool
// DropIncoming filter incoming packets from external sources to host
DropIncoming(packetData []byte) bool
// SetNetwork of the wireguard interface to which filtering applied
SetNetwork(*net.IPNet)
}
// DeviceWrapper to override Read or Write of packets
type DeviceWrapper struct {
tun.Device
filter PacketFilter
mutex sync.RWMutex
}
// newDeviceWrapper constructor function
func newDeviceWrapper(device tun.Device) *DeviceWrapper {
return &DeviceWrapper{
Device: device,
}
}
// Read wraps read method with filtering feature
func (d *DeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
if n, err = d.Device.Read(bufs, sizes, offset); err != nil {
return 0, err
}
d.mutex.RLock()
filter := d.filter
d.mutex.RUnlock()
if filter == nil {
return
}
for i := 0; i < n; i++ {
if filter.DropOutgoing(bufs[i][offset : offset+sizes[i]]) {
bufs = append(bufs[:i], bufs[i+1:]...)
sizes = append(sizes[:i], sizes[i+1:]...)
n--
i--
}
}
return n, nil
}
// Write wraps write method with filtering feature
func (d *DeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
d.mutex.RLock()
filter := d.filter
d.mutex.RUnlock()
if filter == nil {
return d.Device.Write(bufs, offset)
}
filteredBufs := make([][]byte, 0, len(bufs))
dropped := 0
for _, buf := range bufs {
if !filter.DropIncoming(buf[offset:]) {
filteredBufs = append(filteredBufs, buf)
dropped++
}
}
n, err := d.Device.Write(filteredBufs, offset)
n += dropped
return n, err
}
// SetFiltering sets packet filter to device
func (d *DeviceWrapper) SetFiltering(filter PacketFilter) {
d.mutex.Lock()
d.filter = filter
d.mutex.Unlock()
}

View File

@ -0,0 +1,216 @@
package iface
import (
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
mocks "github.com/netbirdio/netbird/iface/mocks"
)
func TestDeviceWrapperRead(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
tun := mocks.NewMockDevice(ctrl)
filter := mocks.NewMockPacketFilter(ctrl)
mockBufs := [][]byte{{}}
mockSizes := []int{0}
mockOffset := 0
t.Run("read ICMP", func(t *testing.T) {
ipLayer := &layers.IPv4{
Version: 4,
TTL: 64,
Protocol: layers.IPProtocolICMPv4,
SrcIP: net.IP{192, 168, 0, 1},
DstIP: net.IP{100, 200, 0, 1},
}
icmpLayer := &layers.ICMPv4{
TypeCode: layers.CreateICMPv4TypeCode(layers.ICMPv4TypeEchoRequest, 0),
Id: 1,
Seq: 1,
}
buffer := gopacket.NewSerializeBuffer()
err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{},
ipLayer,
icmpLayer,
)
if err != nil {
t.Errorf("serialize packet: %v", err)
return
}
tun.EXPECT().Read(mockBufs, mockSizes, mockOffset).
DoAndReturn(func(bufs [][]byte, sizes []int, offset int) (int, error) {
bufs[0] = buffer.Bytes()
sizes[0] = len(bufs[0])
return 1, nil
})
wrapped := newDeviceWrapper(tun)
bufs := [][]byte{{}}
sizes := []int{0}
offset := 0
n, err := wrapped.Read(bufs, sizes, offset)
if err != nil {
t.Errorf("unexpeted error: %v", err)
return
}
if n != 1 {
t.Errorf("expected n=1, got %d", n)
return
}
})
t.Run("write TCP", func(t *testing.T) {
ipLayer := &layers.IPv4{
Version: 4,
TTL: 64,
Protocol: layers.IPProtocolICMPv4,
SrcIP: net.IP{100, 200, 0, 9},
DstIP: net.IP{100, 200, 0, 10},
}
// create TCP layer packet
tcpLayer := &layers.TCP{
SrcPort: layers.TCPPort(34423),
DstPort: layers.TCPPort(8080),
}
buffer := gopacket.NewSerializeBuffer()
err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{},
ipLayer,
tcpLayer,
)
if err != nil {
t.Errorf("serialize packet: %v", err)
return
}
mockBufs[0] = buffer.Bytes()
tun.EXPECT().Write(mockBufs, 0).Return(1, nil)
wrapped := newDeviceWrapper(tun)
bufs := [][]byte{buffer.Bytes()}
n, err := wrapped.Write(bufs, 0)
if err != nil {
t.Errorf("unexpeted error: %v", err)
return
}
if n != 1 {
t.Errorf("expected n=1, got %d", n)
return
}
})
t.Run("drop write UDP package", func(t *testing.T) {
ipLayer := &layers.IPv4{
Version: 4,
TTL: 64,
Protocol: layers.IPProtocolICMPv4,
SrcIP: net.IP{100, 200, 0, 11},
DstIP: net.IP{100, 200, 0, 20},
}
// create TCP layer packet
tcpLayer := &layers.UDP{
SrcPort: layers.UDPPort(27278),
DstPort: layers.UDPPort(53),
}
buffer := gopacket.NewSerializeBuffer()
err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{},
ipLayer,
tcpLayer,
)
if err != nil {
t.Errorf("serialize packet: %v", err)
return
}
mockBufs = [][]byte{}
tun.EXPECT().Write(mockBufs, 0).Return(0, nil)
filter.EXPECT().DropOutput(gomock.Any()).Return(true)
wrapped := newDeviceWrapper(tun)
wrapped.filter = filter
bufs := [][]byte{buffer.Bytes()}
n, err := wrapped.Write(bufs, 0)
if err != nil {
t.Errorf("unexpeted error: %v", err)
return
}
if n != 0 {
t.Errorf("expected n=1, got %d", n)
return
}
})
t.Run("drop read UDP package", func(t *testing.T) {
ipLayer := &layers.IPv4{
Version: 4,
TTL: 64,
Protocol: layers.IPProtocolICMPv4,
SrcIP: net.IP{100, 200, 0, 11},
DstIP: net.IP{100, 200, 0, 20},
}
// create TCP layer packet
tcpLayer := &layers.UDP{
SrcPort: layers.UDPPort(19243),
DstPort: layers.UDPPort(1024),
}
buffer := gopacket.NewSerializeBuffer()
err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{},
ipLayer,
tcpLayer,
)
if err != nil {
t.Errorf("serialize packet: %v", err)
return
}
mockBufs := [][]byte{{}}
mockSizes := []int{0}
mockOffset := 0
tun.EXPECT().Read(mockBufs, mockSizes, mockOffset).
DoAndReturn(func(bufs [][]byte, sizes []int, offset int) (int, error) {
bufs[0] = buffer.Bytes()
sizes[0] = len(bufs[0])
return 1, nil
})
filter.EXPECT().DropInput(gomock.Any()).Return(true)
wrapped := newDeviceWrapper(tun)
wrapped.filter = filter
bufs := [][]byte{{}}
sizes := []int{0}
offset := 0
n, err := wrapped.Read(bufs, sizes, offset)
if err != nil {
t.Errorf("unexpeted error: %v", err)
return
}
if n != 0 {
t.Errorf("expected n=0, got %d", n)
return
}
})
}

View File

@ -1,6 +1,7 @@
package iface
import (
"fmt"
"net"
"sync"
"time"
@ -118,3 +119,17 @@ func (w *WGIface) Close() error {
defer w.mu.Unlock()
return w.tun.Close()
}
// SetFiltering sets packet filters for the userspace impelemntation
func (w *WGIface) SetFiltering(filter PacketFilter) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.tun.wrapper == nil {
return fmt.Errorf("userspace packet filtering not handled on this device")
}
filter.SetNetwork(w.tun.address.Network)
w.tun.wrapper.SetFiltering(filter)
return nil
}

75
iface/mocks/filter.go Normal file
View File

@ -0,0 +1,75 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/netbirdio/netbird/iface (interfaces: PacketFilter)
// Package mocks is a generated GoMock package.
package mocks
import (
net "net"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockPacketFilter is a mock of PacketFilter interface.
type MockPacketFilter struct {
ctrl *gomock.Controller
recorder *MockPacketFilterMockRecorder
}
// MockPacketFilterMockRecorder is the mock recorder for MockPacketFilter.
type MockPacketFilterMockRecorder struct {
mock *MockPacketFilter
}
// NewMockPacketFilter creates a new mock instance.
func NewMockPacketFilter(ctrl *gomock.Controller) *MockPacketFilter {
mock := &MockPacketFilter{ctrl: ctrl}
mock.recorder = &MockPacketFilterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPacketFilter) EXPECT() *MockPacketFilterMockRecorder {
return m.recorder
}
// DropInput mocks base method.
func (m *MockPacketFilter) DropOutgoing(arg0 []byte) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DropOutgoing", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// DropInput indicates an expected call of DropInput.
func (mr *MockPacketFilterMockRecorder) DropInput(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropOutgoing", reflect.TypeOf((*MockPacketFilter)(nil).DropOutgoing), arg0)
}
// DropOutput mocks base method.
func (m *MockPacketFilter) DropIncoming(arg0 []byte) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DropIncoming", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// DropOutput indicates an expected call of DropOutput.
func (mr *MockPacketFilterMockRecorder) DropOutput(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropIncoming", reflect.TypeOf((*MockPacketFilter)(nil).DropIncoming), arg0)
}
// SetNetwork mocks base method.
func (m *MockPacketFilter) SetNetwork(arg0 *net.IPNet) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetNetwork", arg0)
}
// SetNetwork indicates an expected call of SetNetwork.
func (mr *MockPacketFilterMockRecorder) SetNetwork(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNetwork", reflect.TypeOf((*MockPacketFilter)(nil).SetNetwork), arg0)
}

152
iface/mocks/tun.go Normal file
View File

@ -0,0 +1,152 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: golang.zx2c4.com/wireguard/tun (interfaces: Device)
// Package mocks is a generated GoMock package.
package mocks
import (
os "os"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
tun "golang.zx2c4.com/wireguard/tun"
)
// MockDevice is a mock of Device interface.
type MockDevice struct {
ctrl *gomock.Controller
recorder *MockDeviceMockRecorder
}
// MockDeviceMockRecorder is the mock recorder for MockDevice.
type MockDeviceMockRecorder struct {
mock *MockDevice
}
// NewMockDevice creates a new mock instance.
func NewMockDevice(ctrl *gomock.Controller) *MockDevice {
mock := &MockDevice{ctrl: ctrl}
mock.recorder = &MockDeviceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDevice) EXPECT() *MockDeviceMockRecorder {
return m.recorder
}
// BatchSize mocks base method.
func (m *MockDevice) BatchSize() int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BatchSize")
ret0, _ := ret[0].(int)
return ret0
}
// BatchSize indicates an expected call of BatchSize.
func (mr *MockDeviceMockRecorder) BatchSize() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchSize", reflect.TypeOf((*MockDevice)(nil).BatchSize))
}
// Close mocks base method.
func (m *MockDevice) Close() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close.
func (mr *MockDeviceMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDevice)(nil).Close))
}
// Events mocks base method.
func (m *MockDevice) Events() <-chan tun.Event {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Events")
ret0, _ := ret[0].(<-chan tun.Event)
return ret0
}
// Events indicates an expected call of Events.
func (mr *MockDeviceMockRecorder) Events() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Events", reflect.TypeOf((*MockDevice)(nil).Events))
}
// File mocks base method.
func (m *MockDevice) File() *os.File {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "File")
ret0, _ := ret[0].(*os.File)
return ret0
}
// File indicates an expected call of File.
func (mr *MockDeviceMockRecorder) File() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "File", reflect.TypeOf((*MockDevice)(nil).File))
}
// MTU mocks base method.
func (m *MockDevice) MTU() (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MTU")
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MTU indicates an expected call of MTU.
func (mr *MockDeviceMockRecorder) MTU() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MTU", reflect.TypeOf((*MockDevice)(nil).MTU))
}
// Name mocks base method.
func (m *MockDevice) Name() (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Name")
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Name indicates an expected call of Name.
func (mr *MockDeviceMockRecorder) Name() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockDevice)(nil).Name))
}
// Read mocks base method.
func (m *MockDevice) Read(arg0 [][]byte, arg1 []int, arg2 int) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Read", arg0, arg1, arg2)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Read indicates an expected call of Read.
func (mr *MockDeviceMockRecorder) Read(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockDevice)(nil).Read), arg0, arg1, arg2)
}
// Write mocks base method.
func (m *MockDevice) Write(arg0 [][]byte, arg1 int) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Write", arg0, arg1)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Write indicates an expected call of Write.
func (mr *MockDeviceMockRecorder) Write(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockDevice)(nil).Write), arg0, arg1)
}

View File

@ -22,6 +22,7 @@ type tunDevice struct {
name string
device *device.Device
iceBind *bind.ICEBind
wrapper *DeviceWrapper
}
func newTunDevice(address WGAddress, mtu int, routes []string, tunAdapter TunAdapter, transportNet transport.Net) *tunDevice {
@ -49,9 +50,10 @@ func (t *tunDevice) Create() error {
return err
}
t.name = name
t.wrapper = newDeviceWrapper(tunDevice)
log.Debugf("attaching to interface %v", name)
t.device = device.NewDevice(tunDevice, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
// without this property mobile devices can discover remote endpoints if the configured one was wrong.
// this helps with support for the older NetBird clients that had a hardcoded direct mode
//t.device.DisableSomeRoamingForBrokenMobileSemantics()

View File

@ -23,6 +23,7 @@ type tunDevice struct {
netInterface NetInterface
iceBind *bind.ICEBind
uapi net.Listener
wrapper *DeviceWrapper
close chan struct{}
}
@ -90,9 +91,14 @@ func (c *tunDevice) createWithUserspace() (NetInterface, error) {
if err != nil {
return nil, err
}
c.wrapper = newDeviceWrapper(tunIface)
// We need to create a wireguard-go device and listen to configuration requests
tunDev := device.NewDevice(tunIface, c.iceBind, device.NewLogger(device.LogLevelSilent, "[netbird] "))
tunDev := device.NewDevice(
c.wrapper,
c.iceBind,
device.NewLogger(device.LogLevelSilent, "[netbird] "),
)
err = tunDev.Up()
if err != nil {
_ = tunIface.Close()

View File

@ -23,6 +23,7 @@ type tunDevice struct {
iceBind *bind.ICEBind
mtu int
uapi net.Listener
wrapper *DeviceWrapper
close chan struct{}
}
@ -52,6 +53,8 @@ func (c *tunDevice) createWithUserspace() (NetInterface, error) {
if err != nil {
return nil, err
}
c.wrapper = newDeviceWrapper(tunIface)
// We need to create a wireguard-go device and listen to configuration requests
tunDev := device.NewDevice(tunIface, c.iceBind, device.NewLogger(device.LogLevelSilent, "[netbird] "))
err = tunDev.Up()

View File

@ -119,6 +119,153 @@ func (DeviceAuthorizationFlowProvider) EnumDescriptor() ([]byte, []int) {
return file_management_proto_rawDescGZIP(), []int{17, 0}
}
type FirewallRuleDirection int32
const (
FirewallRule_IN FirewallRuleDirection = 0
FirewallRule_OUT FirewallRuleDirection = 1
)
// Enum value maps for FirewallRuleDirection.
var (
FirewallRuleDirection_name = map[int32]string{
0: "IN",
1: "OUT",
}
FirewallRuleDirection_value = map[string]int32{
"IN": 0,
"OUT": 1,
}
)
func (x FirewallRuleDirection) Enum() *FirewallRuleDirection {
p := new(FirewallRuleDirection)
*p = x
return p
}
func (x FirewallRuleDirection) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (FirewallRuleDirection) Descriptor() protoreflect.EnumDescriptor {
return file_management_proto_enumTypes[2].Descriptor()
}
func (FirewallRuleDirection) Type() protoreflect.EnumType {
return &file_management_proto_enumTypes[2]
}
func (x FirewallRuleDirection) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use FirewallRuleDirection.Descriptor instead.
func (FirewallRuleDirection) EnumDescriptor() ([]byte, []int) {
return file_management_proto_rawDescGZIP(), []int{25, 0}
}
type FirewallRuleAction int32
const (
FirewallRule_ACCEPT FirewallRuleAction = 0
FirewallRule_DROP FirewallRuleAction = 1
)
// Enum value maps for FirewallRuleAction.
var (
FirewallRuleAction_name = map[int32]string{
0: "ACCEPT",
1: "DROP",
}
FirewallRuleAction_value = map[string]int32{
"ACCEPT": 0,
"DROP": 1,
}
)
func (x FirewallRuleAction) Enum() *FirewallRuleAction {
p := new(FirewallRuleAction)
*p = x
return p
}
func (x FirewallRuleAction) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (FirewallRuleAction) Descriptor() protoreflect.EnumDescriptor {
return file_management_proto_enumTypes[3].Descriptor()
}
func (FirewallRuleAction) Type() protoreflect.EnumType {
return &file_management_proto_enumTypes[3]
}
func (x FirewallRuleAction) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use FirewallRuleAction.Descriptor instead.
func (FirewallRuleAction) EnumDescriptor() ([]byte, []int) {
return file_management_proto_rawDescGZIP(), []int{25, 1}
}
type FirewallRuleProtocol int32
const (
FirewallRule_UNKNOWN FirewallRuleProtocol = 0
FirewallRule_ALL FirewallRuleProtocol = 1
FirewallRule_TCP FirewallRuleProtocol = 2
FirewallRule_UDP FirewallRuleProtocol = 3
FirewallRule_ICMP FirewallRuleProtocol = 4
)
// Enum value maps for FirewallRuleProtocol.
var (
FirewallRuleProtocol_name = map[int32]string{
0: "UNKNOWN",
1: "ALL",
2: "TCP",
3: "UDP",
4: "ICMP",
}
FirewallRuleProtocol_value = map[string]int32{
"UNKNOWN": 0,
"ALL": 1,
"TCP": 2,
"UDP": 3,
"ICMP": 4,
}
)
func (x FirewallRuleProtocol) Enum() *FirewallRuleProtocol {
p := new(FirewallRuleProtocol)
*p = x
return p
}
func (x FirewallRuleProtocol) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (FirewallRuleProtocol) Descriptor() protoreflect.EnumDescriptor {
return file_management_proto_enumTypes[4].Descriptor()
}
func (FirewallRuleProtocol) Type() protoreflect.EnumType {
return &file_management_proto_enumTypes[4]
}
func (x FirewallRuleProtocol) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use FirewallRuleProtocol.Descriptor instead.
func (FirewallRuleProtocol) EnumDescriptor() ([]byte, []int) {
return file_management_proto_rawDescGZIP(), []int{25, 2}
}
type EncryptedMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -995,6 +1142,8 @@ type NetworkMap struct {
DNSConfig *DNSConfig `protobuf:"bytes,6,opt,name=DNSConfig,proto3" json:"DNSConfig,omitempty"`
// RemotePeerConfig represents a list of remote peers that the receiver can connect to
OfflinePeers []*RemotePeerConfig `protobuf:"bytes,7,rep,name=offlinePeers,proto3" json:"offlinePeers,omitempty"`
// FirewallRule represents a list of firewall rules to be applied to peer
FirewallRules []*FirewallRule `protobuf:"bytes,8,rep,name=FirewallRules,proto3" json:"FirewallRules,omitempty"`
}
func (x *NetworkMap) Reset() {
@ -1078,6 +1227,13 @@ func (x *NetworkMap) GetOfflinePeers() []*RemotePeerConfig {
return nil
}
func (x *NetworkMap) GetFirewallRules() []*FirewallRule {
if x != nil {
return x.FirewallRules
}
return nil
}
// RemotePeerConfig represents a configuration of a remote peer.
// The properties are used to configure WireGuard Peers sections
type RemotePeerConfig struct {
@ -1849,6 +2005,86 @@ func (x *NameServer) GetPort() int64 {
return 0
}
// FirewallRule represents a firewall rule
type FirewallRule struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PeerIP string `protobuf:"bytes,1,opt,name=PeerIP,proto3" json:"PeerIP,omitempty"`
Direction FirewallRuleDirection `protobuf:"varint,2,opt,name=Direction,proto3,enum=management.FirewallRuleDirection" json:"Direction,omitempty"`
Action FirewallRuleAction `protobuf:"varint,3,opt,name=Action,proto3,enum=management.FirewallRuleAction" json:"Action,omitempty"`
Protocol FirewallRuleProtocol `protobuf:"varint,4,opt,name=Protocol,proto3,enum=management.FirewallRuleProtocol" json:"Protocol,omitempty"`
Port string `protobuf:"bytes,5,opt,name=Port,proto3" json:"Port,omitempty"`
}
func (x *FirewallRule) Reset() {
*x = FirewallRule{}
if protoimpl.UnsafeEnabled {
mi := &file_management_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FirewallRule) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FirewallRule) ProtoMessage() {}
func (x *FirewallRule) ProtoReflect() protoreflect.Message {
mi := &file_management_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FirewallRule.ProtoReflect.Descriptor instead.
func (*FirewallRule) Descriptor() ([]byte, []int) {
return file_management_proto_rawDescGZIP(), []int{25}
}
func (x *FirewallRule) GetPeerIP() string {
if x != nil {
return x.PeerIP
}
return ""
}
func (x *FirewallRule) GetDirection() FirewallRuleDirection {
if x != nil {
return x.Direction
}
return FirewallRule_IN
}
func (x *FirewallRule) GetAction() FirewallRuleAction {
if x != nil {
return x.Action
}
return FirewallRule_ACCEPT
}
func (x *FirewallRule) GetProtocol() FirewallRuleProtocol {
if x != nil {
return x.Protocol
}
return FirewallRule_UNKNOWN
}
func (x *FirewallRule) GetPort() string {
if x != nil {
return x.Port
}
return ""
}
var File_management_proto protoreflect.FileDescriptor
var file_management_proto_rawDesc = []byte{
@ -1966,7 +2202,7 @@ var file_management_proto_rawDesc = []byte{
0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0xee, 0x02, 0x0a, 0x0a, 0x4e, 0x65, 0x74,
0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0xae, 0x03, 0x0a, 0x0a, 0x4e, 0x65, 0x74,
0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61,
0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12,
0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20,
@ -1989,7 +2225,11 @@ var file_management_proto_rawDesc = []byte{
0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74,
0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6f, 0x66, 0x66,
0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65,
0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x46, 0x69, 0x72,
0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69,
0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x46, 0x69, 0x72, 0x65,
0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65,
0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a,
0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c,
@ -2083,32 +2323,55 @@ var file_management_proto_rawDesc = []byte{
0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50,
0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x32,
0xf7, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c,
0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22,
0xf0, 0x02, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65,
0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61,
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c,
0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x41, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c,
0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f,
0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a,
0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52,
0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a,
0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12,
0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50,
0x10, 0x04, 0x32, 0xf7, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69,
0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a,
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12,
0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65,
0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65,
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69,
0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61,
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,
0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74,
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c,
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d,
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04,
0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65,
0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65,
0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a,
0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61,
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -2123,81 +2386,89 @@ func file_management_proto_rawDescGZIP() []byte {
return file_management_proto_rawDescData
}
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
var file_management_proto_goTypes = []interface{}{
(HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol
(DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider
(*EncryptedMessage)(nil), // 2: management.EncryptedMessage
(*SyncRequest)(nil), // 3: management.SyncRequest
(*SyncResponse)(nil), // 4: management.SyncResponse
(*LoginRequest)(nil), // 5: management.LoginRequest
(*PeerKeys)(nil), // 6: management.PeerKeys
(*PeerSystemMeta)(nil), // 7: management.PeerSystemMeta
(*LoginResponse)(nil), // 8: management.LoginResponse
(*ServerKeyResponse)(nil), // 9: management.ServerKeyResponse
(*Empty)(nil), // 10: management.Empty
(*WiretrusteeConfig)(nil), // 11: management.WiretrusteeConfig
(*HostConfig)(nil), // 12: management.HostConfig
(*ProtectedHostConfig)(nil), // 13: management.ProtectedHostConfig
(*PeerConfig)(nil), // 14: management.PeerConfig
(*NetworkMap)(nil), // 15: management.NetworkMap
(*RemotePeerConfig)(nil), // 16: management.RemotePeerConfig
(*SSHConfig)(nil), // 17: management.SSHConfig
(*DeviceAuthorizationFlowRequest)(nil), // 18: management.DeviceAuthorizationFlowRequest
(*DeviceAuthorizationFlow)(nil), // 19: management.DeviceAuthorizationFlow
(*ProviderConfig)(nil), // 20: management.ProviderConfig
(*Route)(nil), // 21: management.Route
(*DNSConfig)(nil), // 22: management.DNSConfig
(*CustomZone)(nil), // 23: management.CustomZone
(*SimpleRecord)(nil), // 24: management.SimpleRecord
(*NameServerGroup)(nil), // 25: management.NameServerGroup
(*NameServer)(nil), // 26: management.NameServer
(*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp
(FirewallRuleDirection)(0), // 2: management.FirewallRule.direction
(FirewallRuleAction)(0), // 3: management.FirewallRule.action
(FirewallRuleProtocol)(0), // 4: management.FirewallRule.protocol
(*EncryptedMessage)(nil), // 5: management.EncryptedMessage
(*SyncRequest)(nil), // 6: management.SyncRequest
(*SyncResponse)(nil), // 7: management.SyncResponse
(*LoginRequest)(nil), // 8: management.LoginRequest
(*PeerKeys)(nil), // 9: management.PeerKeys
(*PeerSystemMeta)(nil), // 10: management.PeerSystemMeta
(*LoginResponse)(nil), // 11: management.LoginResponse
(*ServerKeyResponse)(nil), // 12: management.ServerKeyResponse
(*Empty)(nil), // 13: management.Empty
(*WiretrusteeConfig)(nil), // 14: management.WiretrusteeConfig
(*HostConfig)(nil), // 15: management.HostConfig
(*ProtectedHostConfig)(nil), // 16: management.ProtectedHostConfig
(*PeerConfig)(nil), // 17: management.PeerConfig
(*NetworkMap)(nil), // 18: management.NetworkMap
(*RemotePeerConfig)(nil), // 19: management.RemotePeerConfig
(*SSHConfig)(nil), // 20: management.SSHConfig
(*DeviceAuthorizationFlowRequest)(nil), // 21: management.DeviceAuthorizationFlowRequest
(*DeviceAuthorizationFlow)(nil), // 22: management.DeviceAuthorizationFlow
(*ProviderConfig)(nil), // 23: management.ProviderConfig
(*Route)(nil), // 24: management.Route
(*DNSConfig)(nil), // 25: management.DNSConfig
(*CustomZone)(nil), // 26: management.CustomZone
(*SimpleRecord)(nil), // 27: management.SimpleRecord
(*NameServerGroup)(nil), // 28: management.NameServerGroup
(*NameServer)(nil), // 29: management.NameServer
(*FirewallRule)(nil), // 30: management.FirewallRule
(*timestamppb.Timestamp)(nil), // 31: google.protobuf.Timestamp
}
var file_management_proto_depIdxs = []int32{
11, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
14, // 1: management.SyncResponse.peerConfig:type_name -> management.PeerConfig
16, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig
15, // 3: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap
7, // 4: management.LoginRequest.meta:type_name -> management.PeerSystemMeta
6, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys
11, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
14, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
27, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
12, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig
13, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
12, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig
14, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
17, // 1: management.SyncResponse.peerConfig:type_name -> management.PeerConfig
19, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig
18, // 3: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap
10, // 4: management.LoginRequest.meta:type_name -> management.PeerSystemMeta
9, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys
14, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
17, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
31, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
15, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig
16, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
15, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig
0, // 12: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol
12, // 13: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig
17, // 14: management.PeerConfig.sshConfig:type_name -> management.SSHConfig
14, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
16, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
21, // 17: management.NetworkMap.Routes:type_name -> management.Route
22, // 18: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig
16, // 19: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig
17, // 20: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig
1, // 21: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
20, // 22: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
25, // 23: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup
23, // 24: management.DNSConfig.CustomZones:type_name -> management.CustomZone
24, // 25: management.CustomZone.Records:type_name -> management.SimpleRecord
26, // 26: management.NameServerGroup.NameServers:type_name -> management.NameServer
2, // 27: management.ManagementService.Login:input_type -> management.EncryptedMessage
2, // 28: management.ManagementService.Sync:input_type -> management.EncryptedMessage
10, // 29: management.ManagementService.GetServerKey:input_type -> management.Empty
10, // 30: management.ManagementService.isHealthy:input_type -> management.Empty
2, // 31: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
2, // 32: management.ManagementService.Login:output_type -> management.EncryptedMessage
2, // 33: management.ManagementService.Sync:output_type -> management.EncryptedMessage
9, // 34: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
10, // 35: management.ManagementService.isHealthy:output_type -> management.Empty
2, // 36: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
32, // [32:37] is the sub-list for method output_type
27, // [27:32] is the sub-list for method input_type
27, // [27:27] is the sub-list for extension type_name
27, // [27:27] is the sub-list for extension extendee
0, // [0:27] is the sub-list for field type_name
15, // 13: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig
20, // 14: management.PeerConfig.sshConfig:type_name -> management.SSHConfig
17, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
19, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
24, // 17: management.NetworkMap.Routes:type_name -> management.Route
25, // 18: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig
19, // 19: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig
30, // 20: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule
20, // 21: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig
1, // 22: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
23, // 23: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
28, // 24: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup
26, // 25: management.DNSConfig.CustomZones:type_name -> management.CustomZone
27, // 26: management.CustomZone.Records:type_name -> management.SimpleRecord
29, // 27: management.NameServerGroup.NameServers:type_name -> management.NameServer
2, // 28: management.FirewallRule.Direction:type_name -> management.FirewallRule.direction
3, // 29: management.FirewallRule.Action:type_name -> management.FirewallRule.action
4, // 30: management.FirewallRule.Protocol:type_name -> management.FirewallRule.protocol
5, // 31: management.ManagementService.Login:input_type -> management.EncryptedMessage
5, // 32: management.ManagementService.Sync:input_type -> management.EncryptedMessage
13, // 33: management.ManagementService.GetServerKey:input_type -> management.Empty
13, // 34: management.ManagementService.isHealthy:input_type -> management.Empty
5, // 35: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
5, // 36: management.ManagementService.Login:output_type -> management.EncryptedMessage
5, // 37: management.ManagementService.Sync:output_type -> management.EncryptedMessage
12, // 38: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
13, // 39: management.ManagementService.isHealthy:output_type -> management.Empty
5, // 40: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
36, // [36:41] is the sub-list for method output_type
31, // [31:36] is the sub-list for method input_type
31, // [31:31] is the sub-list for extension type_name
31, // [31:31] is the sub-list for extension extendee
0, // [0:31] is the sub-list for field type_name
}
func init() { file_management_proto_init() }
@ -2506,14 +2777,26 @@ func file_management_proto_init() {
return nil
}
}
file_management_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FirewallRule); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_management_proto_rawDesc,
NumEnums: 2,
NumMessages: 25,
NumEnums: 5,
NumMessages: 26,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -186,6 +186,9 @@ message NetworkMap {
// RemotePeerConfig represents a list of remote peers that the receiver can connect to
repeated RemotePeerConfig offlinePeers = 7;
// FirewallRule represents a list of firewall rules to be applied to peer
repeated FirewallRule FirewallRules = 8;
}
// RemotePeerConfig represents a configuration of a remote peer.
@ -297,4 +300,29 @@ message NameServer {
string IP = 1;
int64 NSType = 2;
int64 Port = 3;
}
}
// FirewallRule represents a firewall rule
message FirewallRule {
string PeerIP = 1;
direction Direction = 2;
action Action = 3;
protocol Protocol = 4;
string Port = 5;
enum direction {
IN = 0;
OUT = 1;
}
enum action {
ACCEPT = 0;
DROP = 1;
}
enum protocol {
UNKNOWN = 0;
ALL = 1;
TCP = 2;
UDP = 3;
ICMP = 4;
}
}

View File

@ -286,7 +286,7 @@ func (a *Account) GetGroup(groupID string) *Group {
// GetPeerNetworkMap returns a group by ID if exists, nil otherwise
func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
aclPeers := a.getPeersByACL(peerID)
aclPeers, firewallRules := a.getPeerConnectionResources(peerID)
// exclude expired peers
var peersToConnect []*Peer
var expiredPeers []*Peer
@ -317,11 +317,12 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
}
return &NetworkMap{
Peers: peersToConnect,
Network: a.Network.Copy(),
Routes: routesUpdate,
DNSConfig: dnsUpdate,
OfflinePeers: expiredPeers,
Peers: peersToConnect,
Network: a.Network.Copy(),
Routes: routesUpdate,
DNSConfig: dnsUpdate,
OfflinePeers: expiredPeers,
FirewallRules: firewallRules,
}
}

View File

@ -919,17 +919,14 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
Enabled: true,
Rules: []*PolicyRule{
{
Enabled: true,
Sources: []string{"group-id"},
Destinations: []string{"group-id"},
Action: PolicyTrafficActionAccept,
Enabled: true,
Sources: []string{"group-id"},
Destinations: []string{"group-id"},
Bidirectional: true,
Action: PolicyTrafficActionAccept,
},
},
}
if err := policy.UpdateQueryFromRules(); err != nil {
t.Errorf("update policy query from rules: %v", err)
return
}
wg := sync.WaitGroup{}
t.Run("save group update", func(t *testing.T) {

View File

@ -129,12 +129,11 @@ func restore(file string) (*FileStore, error) {
store.PrivateDomain2AccountID[account.Domain] = accountID
}
// TODO: policy query generated from the Go template and rule object.
// We need to refactor this part to avoid using templating for policies queries building
// and drop this migration part.
// TODO: delete this block after migration
policies := make(map[string]int, len(account.Policies))
for i, policy := range account.Policies {
policies[policy.ID] = i
policy.UpgradeAndFix()
}
if account.Policies == nil {
account.Policies = make([]*Policy, 0)
@ -145,9 +144,9 @@ func restore(file string) (*FileStore, error) {
log.Errorf("unable to migrate rule to policy: %v", err)
continue
}
if i, ok := policies[policy.ID]; ok {
account.Policies[i] = policy
} else {
// don't update policies from rules, rules deprecated,
// only append not existed rules as part of the migration process
if _, ok := policies[policy.ID]; !ok {
account.Policies = append(account.Policies, policy)
}
}

View File

@ -285,10 +285,7 @@ func TestRestorePolicies_Migration(t *testing.T) {
require.Equal(t, policy.Description,
"This is a default rule that allows connections between all the resources",
"failed to restore a FileStore file - missing Account Policies Description")
expectedPolicy := policy.Copy()
err = expectedPolicy.UpdateQueryFromRules()
require.NoError(t, err, "failed to upldate query")
require.Equal(t, policy.Query, expectedPolicy.Query, "failed to restore a FileStore file - missing Account Policies Query")
require.Len(t, policy.Rules, 1, "failed to restore a FileStore file - missing Account Policy Rules")
require.Equal(t, policy.Rules[0].Action, PolicyTrafficActionAccept, "failed to restore a FileStore file - missing Account Policies Action")
require.Equal(t, policy.Rules[0].Destinations,

View File

@ -436,6 +436,8 @@ func toSyncResponse(config *Config, peer *Peer, turnCredentials *TURNCredentials
offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName)
firewallRules := toProtocolFirewallRules(networkMap.FirewallRules)
return &proto.SyncResponse{
WiretrusteeConfig: wtConfig,
PeerConfig: pConfig,
@ -449,6 +451,7 @@ func toSyncResponse(config *Config, peer *Peer, turnCredentials *TURNCredentials
RemotePeersIsEmpty: len(remotePeers) == 0,
Routes: routesUpdate,
DNSConfig: dnsUpdate,
FirewallRules: firewallRules,
},
}
}

View File

@ -551,49 +551,91 @@ components:
required:
- sources
- destinations
PolicyRule:
PolicyRuleMinimum:
type: object
properties:
id:
description: Rule ID
description: Policy rule ID
type: string
example: ch8i4ug6lnn4g9hqv7mg
name:
description: Rule name identifier
description: Policy rule name identifier
type: string
example: Default
description:
description: Rule friendly description
description: Policy rule friendly description
type: string
example: This is a default rule that allows connections between all the resources
enabled:
description: Rules status
description: Policy rule status
type: boolean
example: true
sources:
description: policy source groups
type: array
items:
$ref: '#/components/schemas/GroupMinimum'
destinations:
description: policy destination groups
type: array
items:
$ref: '#/components/schemas/GroupMinimum'
action:
description: policy accept or drops packets
description: Policy rule accept or drops packets
type: string
enum: ["accept","drop"]
example: accept
bidirectional:
description: Define if the rule is applicable in both directions, sources, and destinations.
type: boolean
example: true
protocol:
description: Policy rule type of the traffic
type: string
enum: ["all", "tcp", "udp", "icmp"]
example: "tcp"
ports:
description: Policy rule affected ports or it ranges list
type: array
items:
type: string
example: [80,443]
required:
- name
- sources
- destinations
- action
- enabled
- bidirectional
- protocol
- action
PolicyRuleUpdate:
allOf:
- $ref: '#/components/schemas/PolicyRuleMinimum'
- type: object
properties:
sources:
description: Policy rule source groups
type: array
items:
type: string
destinations:
description: Policy rule destination groups
type: array
items:
type: string
required:
- sources
- destinations
PolicyRule:
allOf:
- $ref: '#/components/schemas/PolicyRuleMinimum'
- type: object
properties:
sources:
description: Policy rule source groups
type: array
items:
$ref: '#/components/schemas/GroupMinimum'
destinations:
description: Policy rule destination groups
type: array
items:
$ref: '#/components/schemas/GroupMinimum'
required:
- sources
- destinations
PolicyMinimum:
type: object
properties:
id:
description: Policy ID
type: string
name:
description: Policy name identifier
type: string
@ -609,29 +651,35 @@ components:
query:
description: Policy Rego query
type: string
example: package netbird\n\nall[rule] {\n is_peer_in_any_group([\"ch8i4ug6lnn4g9hqv7m0\",\"ch8i4ug6lnn4g9hqv7m0\"])\n rule := {\n rules_from_group(\"ch8i4ug6lnn4g9hqv7m0\", \"dst\", \"accept\", \"\"),\n rules_from_group(\"ch8i4ug6lnn4g9hqv7m0\", \"src\", \"accept\", \"\"),\n }[_][_]\n}\n
rules:
description: Policy rule object for policy UI editor
type: array
items:
$ref: '#/components/schemas/PolicyRule'
required:
- name
- description
- enabled
- query
- rules
PolicyUpdate:
allOf:
- $ref: '#/components/schemas/PolicyMinimum'
- type: object
properties:
rules:
description: Policy rule object for policy UI editor
type: array
items:
$ref: '#/components/schemas/PolicyRuleUpdate'
required:
- rules
Policy:
allOf:
- $ref: '#/components/schemas/PolicyMinimum'
- type: object
properties:
id:
description: Policy ID
type: string
example: ch8i4ug6lnn4g9hqv7mg
rules:
description: Policy rule object for policy UI editor
type: array
items:
$ref: '#/components/schemas/PolicyRule'
required:
- id
- rules
RouteRequest:
type: object
properties:
@ -884,7 +932,7 @@ security:
paths:
/api/accounts:
get:
summary: List all Accounts
summary: List all accounts
description: Returns a list of accounts of a user. Always returns a list of one account.
tags: [ Accounts ]
security:
@ -909,7 +957,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/accounts/{accountId}:
put:
summary: Update an Account
summary: Update an account
description: Update information about an account
tags: [ Accounts ]
security:
@ -950,7 +998,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/users:
get:
summary: List all Users
summary: List all users
description: Returns a list of all users
tags: [ Users ]
security:
@ -980,7 +1028,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a User
summary: Create a user
description: Creates a new service user or sends an invite to a regular user
tags: [ Users ]
security:
@ -1009,7 +1057,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/users/{userId}:
put:
summary: Update a User
summary: Update a user
description: Update information about a User
tags: [ Users ]
security:
@ -1044,8 +1092,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a User
description: Delete a User
summary: Delete a user
description: Delete a user
tags: [ Users ]
security:
- BearerAuth: [ ]
@ -1071,7 +1119,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/users/{userId}/tokens:
get:
summary: List all Tokens
summary: List all tokens
description: Returns a list of all tokens for a user
tags: [ Tokens ]
security:
@ -1102,7 +1150,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Token
summary: Create a token
description: Create a new token for a user
tags: [ Tokens ]
security:
@ -1138,7 +1186,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/users/{userId}/tokens/{tokenId}:
get:
summary: Retrieve a Token
summary: Retrieve a token
description: Returns a specific token for a user
tags: [ Tokens ]
security:
@ -1173,7 +1221,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Token
summary: Delete a token
description: Delete a token for a user
tags: [ Tokens ]
security:
@ -1206,7 +1254,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/peers:
get:
summary: List all Peers
summary: List all peers
description: Returns a list of all peers
tags: [ Peers ]
security:
@ -1231,7 +1279,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/peers/{peerId}:
get:
summary: Retrieve a Peer
summary: Retrieve a peer
description: Get information about a peer
tags: [ Peers ]
security:
@ -1260,7 +1308,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Peer
summary: Update a peer
description: Update information about a peer
tags: [ Peers ]
security:
@ -1295,7 +1343,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Peer
summary: Delete a peer
description: Delete a peer
tags: [ Peers ]
security:
@ -1322,7 +1370,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/setup-keys:
get:
summary: List all Setup Keys
summary: List all setup keys
description: Returns a list of all Setup Keys
tags: [ Setup Keys ]
security:
@ -1346,8 +1394,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Setup Key
description: Creates a Setup Key
summary: Create a setup key
description: Creates a setup key
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
@ -1375,8 +1423,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/setup-keys/{keyId}:
get:
summary: Retrieve a Setup Key
description: Get information about a Setup Key
summary: Retrieve a setup key
description: Get information about a setup key
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
@ -1404,8 +1452,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Setup Key
description: Update information about a Setup Key
summary: Update a setup key
description: Update information about a setup key
tags: [ Setup Keys ]
security:
- BearerAuth: [ ]
@ -1440,8 +1488,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/groups:
get:
summary: List all Groups
description: Returns a list of all Groups
summary: List all groups
description: Returns a list of all groups
tags: [ Groups ]
security:
- BearerAuth: [ ]
@ -1464,8 +1512,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Group
description: Creates a Group
summary: Create a group
description: Creates a group
tags: [ Groups ]
security:
- BearerAuth: [ ]
@ -1493,8 +1541,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/groups/{groupId}:
get:
summary: Retrieve a Group
description: Get information about a Group
summary: Retrieve a group
description: Get information about a group
tags: [ Groups ]
security:
- BearerAuth: [ ]
@ -1522,8 +1570,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Group
description: Update/Replace a Group
summary: Update a group
description: Update/Replace a group
tags: [ Groups ]
security:
- BearerAuth: [ ]
@ -1558,7 +1606,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Group
description: Delete a Group
description: Delete a group
tags: [ Groups ]
security:
- BearerAuth: [ ]
@ -1584,8 +1632,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/rules:
get:
summary: List all Rules
description: Returns a list of all Rules
summary: List all rules
description: Returns a list of all rules
tags: [ Rules ]
security:
- BearerAuth: [ ]
@ -1608,8 +1656,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Rule
description: Creates a Rule
summary: Create a rule
description: Creates a rule
tags: [ Rules ]
security:
- BearerAuth: [ ]
@ -1629,8 +1677,8 @@ paths:
$ref: '#/components/schemas/Rule'
/api/rules/{ruleId}:
get:
summary: Retrieve a Rule
description: Get information about a Rules
summary: Retrieve a rule
description: Get information about a rules
tags: [ Rules ]
security:
- BearerAuth: [ ]
@ -1658,8 +1706,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Rule
description: Update/Replace a Rule
summary: Update a rule
description: Update/Replace a rule
tags: [ Rules ]
security:
- BearerAuth: [ ]
@ -1693,8 +1741,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Rule
description: Delete a Rule
summary: Delete a rule
description: Delete a rule
tags: [ Rules ]
security:
- BearerAuth: [ ]
@ -1720,8 +1768,8 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/policies:
get:
summary: List all Policies
description: Returns a list of all Policies
summary: List all policies
description: Returns a list of all policies
tags: [ Policies ]
security:
- BearerAuth: [ ]
@ -1744,8 +1792,8 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Policy
description: Creates a Policy
summary: Create a policy
description: Creates a policy
tags: [ Policies ]
security:
- BearerAuth: [ ]
@ -1755,7 +1803,7 @@ paths:
content:
'application/json':
schema:
$ref: '#/components/schemas/PolicyMinimum'
$ref: '#/components/schemas/PolicyUpdate'
responses:
'200':
description: A Policy Object
@ -1765,7 +1813,7 @@ paths:
$ref: '#/components/schemas/Policy'
/api/policies/{policyId}:
get:
summary: Retrieve a Policy
summary: Retrieve a policy
description: Get information about a Policies
tags: [ Policies ]
security:
@ -1794,7 +1842,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Policy
summary: Update a policy
description: Update/Replace a Policy
tags: [ Policies ]
security:
@ -1812,7 +1860,7 @@ paths:
content:
'application/json':
schema:
$ref: '#/components/schemas/PolicyMinimum'
$ref: '#/components/schemas/PolicyUpdate'
responses:
'200':
description: A Policy object
@ -1830,7 +1878,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Policy
description: Delete a Policy
description: Delete a policy
tags: [ Policies ]
security:
- BearerAuth: [ ]
@ -1856,7 +1904,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/routes:
get:
summary: List all Routes
summary: List all routes
description: Returns a list of all routes
tags: [ Routes ]
security:
@ -1880,7 +1928,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Route
summary: Create a route
description: Creates a Route
tags: [ Routes ]
security:
@ -1910,7 +1958,7 @@ paths:
/api/routes/{routeId}:
get:
summary: Retrieve a Route
summary: Retrieve a route
description: Get information about a Routes
tags: [ Routes ]
security:
@ -1939,7 +1987,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Route
summary: Update a route
description: Update/Replace a Route
tags: [ Routes ]
security:
@ -1975,7 +2023,7 @@ paths:
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Route
description: Delete a Route
description: Delete a route
tags: [ Routes ]
security:
- BearerAuth: [ ]
@ -2001,7 +2049,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/dns/nameservers:
get:
summary: List all Nameserver Groups
summary: List all nameserver groups
description: Returns a list of all Nameserver Groups
tags: [ DNS ]
security:
@ -2025,7 +2073,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
post:
summary: Create a Nameserver Group
summary: Create a nameserver group
description: Creates a Nameserver Group
tags: [ DNS ]
security:
@ -2052,9 +2100,10 @@ paths:
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/dns/nameservers/{nsgroupId}:
get:
summary: Retrieve a Nameserver Group
summary: Retrieve a nameserver group
description: Get information about a Nameserver Groups
tags: [ DNS ]
security:
@ -2083,7 +2132,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update a Nameserver Group
summary: Update a nameserver group
description: Update/Replace a Nameserver Group
tags: [ DNS ]
security:
@ -2118,7 +2167,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
delete:
summary: Delete a Nameserver Group
summary: Delete a nameserver group
description: Delete a Nameserver Group
tags: [ DNS ]
security:
@ -2143,9 +2192,10 @@ paths:
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/dns/settings:
get:
summary: Retrieve DNS Settings
summary: Retrieve DNS settings
description: Returns a DNS settings object
tags: [ DNS ]
security:
@ -2168,7 +2218,7 @@ paths:
'500':
"$ref": "#/components/responses/internal_error"
put:
summary: Update DNS Settings
summary: Update DNS settings
description: Updates a DNS settings object
tags: [ DNS ]
security:
@ -2197,7 +2247,7 @@ paths:
"$ref": "#/components/responses/internal_error"
/api/events:
get:
summary: List all Events
summary: List all events
description: Returns a list of all events
tags: [ Events ]
security:

View File

@ -72,6 +72,42 @@ const (
PolicyRuleActionDrop PolicyRuleAction = "drop"
)
// Defines values for PolicyRuleProtocol.
const (
PolicyRuleProtocolAll PolicyRuleProtocol = "all"
PolicyRuleProtocolIcmp PolicyRuleProtocol = "icmp"
PolicyRuleProtocolTcp PolicyRuleProtocol = "tcp"
PolicyRuleProtocolUdp PolicyRuleProtocol = "udp"
)
// Defines values for PolicyRuleMinimumAction.
const (
PolicyRuleMinimumActionAccept PolicyRuleMinimumAction = "accept"
PolicyRuleMinimumActionDrop PolicyRuleMinimumAction = "drop"
)
// Defines values for PolicyRuleMinimumProtocol.
const (
PolicyRuleMinimumProtocolAll PolicyRuleMinimumProtocol = "all"
PolicyRuleMinimumProtocolIcmp PolicyRuleMinimumProtocol = "icmp"
PolicyRuleMinimumProtocolTcp PolicyRuleMinimumProtocol = "tcp"
PolicyRuleMinimumProtocolUdp PolicyRuleMinimumProtocol = "udp"
)
// Defines values for PolicyRuleUpdateAction.
const (
PolicyRuleUpdateActionAccept PolicyRuleUpdateAction = "accept"
PolicyRuleUpdateActionDrop PolicyRuleUpdateAction = "drop"
)
// Defines values for PolicyRuleUpdateProtocol.
const (
PolicyRuleUpdateProtocolAll PolicyRuleUpdateProtocol = "all"
PolicyRuleUpdateProtocolIcmp PolicyRuleUpdateProtocol = "icmp"
PolicyRuleUpdateProtocolTcp PolicyRuleUpdateProtocol = "tcp"
PolicyRuleUpdateProtocolUdp PolicyRuleUpdateProtocol = "udp"
)
// Defines values for UserStatus.
const (
UserStatusActive UserStatus = "active"
@ -344,7 +380,7 @@ type Policy struct {
Enabled bool `json:"enabled"`
// Id Policy ID
Id string `json:"id"`
Id *string `json:"id,omitempty"`
// Name Policy name identifier
Name string `json:"name"`
@ -364,6 +400,138 @@ type PolicyMinimum struct {
// Enabled Policy status
Enabled bool `json:"enabled"`
// Id Policy ID
Id *string `json:"id,omitempty"`
// Name Policy name identifier
Name string `json:"name"`
// Query Policy Rego query
Query string `json:"query"`
}
// PolicyRule defines model for PolicyRule.
type PolicyRule struct {
// Action Policy rule accept or drops packets
Action PolicyRuleAction `json:"action"`
// Bidirectional Define if the rule is applicable in both directions, sources, and destinations.
Bidirectional bool `json:"bidirectional"`
// Description Policy rule friendly description
Description *string `json:"description,omitempty"`
// Destinations Policy rule destination groups
Destinations []GroupMinimum `json:"destinations"`
// Enabled Policy rule status
Enabled bool `json:"enabled"`
// Id Policy rule ID
Id *string `json:"id,omitempty"`
// Name Policy rule name identifier
Name string `json:"name"`
// Ports Policy rule affected ports or it ranges list
Ports *[]string `json:"ports,omitempty"`
// Protocol Policy rule type of the traffic
Protocol PolicyRuleProtocol `json:"protocol"`
// Sources Policy rule source groups
Sources []GroupMinimum `json:"sources"`
}
// PolicyRuleAction Policy rule accept or drops packets
type PolicyRuleAction string
// PolicyRuleProtocol Policy rule type of the traffic
type PolicyRuleProtocol string
// PolicyRuleMinimum defines model for PolicyRuleMinimum.
type PolicyRuleMinimum struct {
// Action Policy rule accept or drops packets
Action PolicyRuleMinimumAction `json:"action"`
// Bidirectional Define if the rule is applicable in both directions, sources, and destinations.
Bidirectional bool `json:"bidirectional"`
// Description Policy rule friendly description
Description *string `json:"description,omitempty"`
// Enabled Policy rule status
Enabled bool `json:"enabled"`
// Id Policy rule ID
Id *string `json:"id,omitempty"`
// Name Policy rule name identifier
Name string `json:"name"`
// Ports Policy rule affected ports or it ranges list
Ports *[]string `json:"ports,omitempty"`
// Protocol Policy rule type of the traffic
Protocol PolicyRuleMinimumProtocol `json:"protocol"`
}
// PolicyRuleMinimumAction Policy rule accept or drops packets
type PolicyRuleMinimumAction string
// PolicyRuleMinimumProtocol Policy rule type of the traffic
type PolicyRuleMinimumProtocol string
// PolicyRuleUpdate defines model for PolicyRuleUpdate.
type PolicyRuleUpdate struct {
// Action Policy rule accept or drops packets
Action PolicyRuleUpdateAction `json:"action"`
// Bidirectional Define if the rule is applicable in both directions, sources, and destinations.
Bidirectional bool `json:"bidirectional"`
// Description Policy rule friendly description
Description *string `json:"description,omitempty"`
// Destinations Policy rule destination groups
Destinations []string `json:"destinations"`
// Enabled Policy rule status
Enabled bool `json:"enabled"`
// Id Policy rule ID
Id *string `json:"id,omitempty"`
// Name Policy rule name identifier
Name string `json:"name"`
// Ports Policy rule affected ports or it ranges list
Ports *[]string `json:"ports,omitempty"`
// Protocol Policy rule type of the traffic
Protocol PolicyRuleUpdateProtocol `json:"protocol"`
// Sources Policy rule source groups
Sources []string `json:"sources"`
}
// PolicyRuleUpdateAction Policy rule accept or drops packets
type PolicyRuleUpdateAction string
// PolicyRuleUpdateProtocol Policy rule type of the traffic
type PolicyRuleUpdateProtocol string
// PolicyUpdate defines model for PolicyUpdate.
type PolicyUpdate struct {
// Description Policy friendly description
Description string `json:"description"`
// Enabled Policy status
Enabled bool `json:"enabled"`
// Id Policy ID
Id *string `json:"id,omitempty"`
// Name Policy name identifier
Name string `json:"name"`
@ -371,36 +539,9 @@ type PolicyMinimum struct {
Query string `json:"query"`
// Rules Policy rule object for policy UI editor
Rules []PolicyRule `json:"rules"`
Rules []PolicyRuleUpdate `json:"rules"`
}
// PolicyRule defines model for PolicyRule.
type PolicyRule struct {
// Action policy accept or drops packets
Action PolicyRuleAction `json:"action"`
// Description Rule friendly description
Description *string `json:"description,omitempty"`
// Destinations policy destination groups
Destinations []GroupMinimum `json:"destinations"`
// Enabled Rules status
Enabled bool `json:"enabled"`
// Id Rule ID
Id *string `json:"id,omitempty"`
// Name Rule name identifier
Name string `json:"name"`
// Sources policy source groups
Sources []GroupMinimum `json:"sources"`
}
// PolicyRuleAction policy accept or drops packets
type PolicyRuleAction string
// Route defines model for Route.
type Route struct {
// Description Route description
@ -680,10 +821,10 @@ type PutApiGroupsGroupIdJSONRequestBody = GroupRequest
type PutApiPeersPeerIdJSONRequestBody = PeerRequest
// PostApiPoliciesJSONRequestBody defines body for PostApiPolicies for application/json ContentType.
type PostApiPoliciesJSONRequestBody = PolicyMinimum
type PostApiPoliciesJSONRequestBody = PolicyUpdate
// PutApiPoliciesPolicyIdJSONRequestBody defines body for PutApiPoliciesPolicyId for application/json ContentType.
type PutApiPoliciesPolicyIdJSONRequestBody = PolicyMinimum
type PutApiPoliciesPolicyIdJSONRequestBody = PolicyUpdate
// PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType.
type PostApiRoutesJSONRequestBody = RouteRequest

View File

@ -6,7 +6,6 @@ import (
"github.com/gorilla/mux"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/api"
@ -47,7 +46,17 @@ func (h *Policies) GetAllPolicies(w http.ResponseWriter, r *http.Request) {
return
}
util.WriteJSONObject(w, accountPolicies)
policies := []*api.Policy{}
for _, policy := range accountPolicies {
resp := toPolicyResponse(account, policy)
if len(resp.Rules) == 0 {
util.WriteError(status.Errorf(status.Internal, "no rules in the policy"), w)
return
}
policies = append(policies, resp)
}
util.WriteJSONObject(w, policies)
}
// UpdatePolicy handles update to a policy identified by a given ID
@ -78,63 +87,7 @@ func (h *Policies) UpdatePolicy(w http.ResponseWriter, r *http.Request) {
return
}
var req api.PutApiPoliciesPolicyIdJSONRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
return
}
if req.Name == "" {
util.WriteError(status.Errorf(status.InvalidArgument, "policy name shouldn't be empty"), w)
return
}
policy := server.Policy{
ID: policyID,
Name: req.Name,
Enabled: req.Enabled,
Description: req.Description,
Query: req.Query,
}
if req.Rules != nil {
for _, r := range req.Rules {
pr := server.PolicyRule{
Destinations: groupMinimumsToStrings(account, r.Destinations),
Sources: groupMinimumsToStrings(account, r.Sources),
Name: r.Name,
}
pr.Enabled = r.Enabled
if r.Description != nil {
pr.Description = *r.Description
}
if r.Id != nil {
pr.ID = *r.Id
}
switch r.Action {
case api.PolicyRuleActionAccept:
pr.Action = server.PolicyTrafficActionAccept
case api.PolicyRuleActionDrop:
pr.Action = server.PolicyTrafficActionDrop
default:
util.WriteError(status.Errorf(status.InvalidArgument, "unknown action type"), w)
return
}
policy.Rules = append(policy.Rules, &pr)
}
}
if err := policy.UpdateQueryFromRules(); err != nil {
log.Errorf("failed to update policy query: %v", err)
util.WriteError(err, w)
return
}
if err = h.accountManager.SavePolicy(account.Id, user.Id, &policy); err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, toPolicyResponse(account, &policy))
h.savePolicy(w, r, account, user, policyID)
}
// CreatePolicy handles policy creation request
@ -146,9 +99,19 @@ func (h *Policies) CreatePolicy(w http.ResponseWriter, r *http.Request) {
return
}
var req api.PostApiPoliciesJSONRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
h.savePolicy(w, r, account, user, "")
}
// savePolicy handles policy creation and update
func (h *Policies) savePolicy(
w http.ResponseWriter,
r *http.Request,
account *server.Account,
user *server.User,
policyID string,
) {
var req api.PutApiPoliciesPolicyIdJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
return
}
@ -158,49 +121,97 @@ func (h *Policies) CreatePolicy(w http.ResponseWriter, r *http.Request) {
return
}
policy := &server.Policy{
ID: xid.New().String(),
if len(req.Rules) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "policy rules shouldn't be empty"), w)
return
}
if policyID == "" {
policyID = xid.New().String()
}
policy := server.Policy{
ID: policyID,
Name: req.Name,
Enabled: req.Enabled,
Description: req.Description,
Query: req.Query,
}
for _, r := range req.Rules {
pr := server.PolicyRule{
ID: policyID, //TODO: when policy can contain multiple rules, need refactor
Name: r.Name,
Destinations: groupMinimumsToStrings(account, r.Destinations),
Sources: groupMinimumsToStrings(account, r.Sources),
Bidirectional: r.Bidirectional,
}
if req.Rules != nil {
for _, r := range req.Rules {
pr := server.PolicyRule{
ID: xid.New().String(),
Destinations: groupMinimumsToStrings(account, r.Destinations),
Sources: groupMinimumsToStrings(account, r.Sources),
Name: r.Name,
}
pr.Enabled = r.Enabled
if r.Description != nil {
pr.Description = *r.Description
}
switch r.Action {
case api.PolicyRuleActionAccept:
pr.Action = server.PolicyTrafficActionAccept
case api.PolicyRuleActionDrop:
pr.Action = server.PolicyTrafficActionDrop
default:
util.WriteError(status.Errorf(status.InvalidArgument, "unknown action type"), w)
pr.Enabled = r.Enabled
if r.Description != nil {
pr.Description = *r.Description
}
switch r.Action {
case api.PolicyRuleUpdateActionAccept:
pr.Action = server.PolicyTrafficActionAccept
case api.PolicyRuleUpdateActionDrop:
pr.Action = server.PolicyTrafficActionDrop
default:
util.WriteError(status.Errorf(status.InvalidArgument, "unknown action type"), w)
return
}
switch r.Protocol {
case api.PolicyRuleUpdateProtocolAll:
pr.Protocol = server.PolicyRuleProtocolALL
case api.PolicyRuleUpdateProtocolTcp:
pr.Protocol = server.PolicyRuleProtocolTCP
case api.PolicyRuleUpdateProtocolUdp:
pr.Protocol = server.PolicyRuleProtocolUDP
case api.PolicyRuleUpdateProtocolIcmp:
pr.Protocol = server.PolicyRuleProtocolICMP
default:
util.WriteError(status.Errorf(status.InvalidArgument, "unknown protocol type: %v", r.Protocol), w)
return
}
if r.Ports != nil && len(*r.Ports) != 0 {
ports := *r.Ports
pr.Ports = ports[:]
}
// validate policy object
switch pr.Protocol {
case server.PolicyRuleProtocolALL, server.PolicyRuleProtocolICMP:
if len(pr.Ports) != 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "for ALL or ICMP protocol ports is not allowed"), w)
return
}
if !pr.Bidirectional {
util.WriteError(status.Errorf(status.InvalidArgument, "for ALL or ICMP protocol type flow can be only bi-directional"), w)
return
}
case server.PolicyRuleProtocolTCP, server.PolicyRuleProtocolUDP:
if !pr.Bidirectional && len(pr.Ports) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "for ALL or ICMP protocol type flow can be only bi-directional"), w)
return
}
policy.Rules = append(policy.Rules, &pr)
}
policy.Rules = append(policy.Rules, &pr)
}
if err := policy.UpdateQueryFromRules(); err != nil {
if err := h.accountManager.SavePolicy(account.Id, user.Id, &policy); err != nil {
util.WriteError(err, w)
return
}
if err = h.accountManager.SavePolicy(account.Id, user.Id, policy); err != nil {
util.WriteError(err, w)
resp := toPolicyResponse(account, &policy)
if len(resp.Rules) == 0 {
util.WriteError(status.Errorf(status.Internal, "no rules in the policy"), w)
return
}
util.WriteJSONObject(w, toPolicyResponse(account, policy))
util.WriteJSONObject(w, resp)
}
// DeletePolicy handles policy deletion request
@ -252,7 +263,13 @@ func (h *Policies) GetPolicy(w http.ResponseWriter, r *http.Request) {
return
}
util.WriteJSONObject(w, toPolicyResponse(account, policy))
resp := toPolicyResponse(account, policy)
if len(resp.Rules) == 0 {
util.WriteError(status.Errorf(status.Internal, "no rules in the policy"), w)
return
}
util.WriteJSONObject(w, resp)
default:
util.WriteError(status.Errorf(status.NotFound, "method not found"), w)
}
@ -261,22 +278,24 @@ func (h *Policies) GetPolicy(w http.ResponseWriter, r *http.Request) {
func toPolicyResponse(account *server.Account, policy *server.Policy) *api.Policy {
cache := make(map[string]api.GroupMinimum)
ap := &api.Policy{
Id: policy.ID,
Id: &policy.ID,
Name: policy.Name,
Description: policy.Description,
Enabled: policy.Enabled,
Query: policy.Query,
}
if len(policy.Rules) == 0 {
return ap
}
for _, r := range policy.Rules {
rule := api.PolicyRule{
Id: &r.ID,
Name: r.Name,
Enabled: r.Enabled,
Description: &r.Description,
Id: &r.ID,
Name: r.Name,
Enabled: r.Enabled,
Description: &r.Description,
Bidirectional: r.Bidirectional,
Protocol: api.PolicyRuleProtocol(r.Protocol),
Action: api.PolicyRuleAction(r.Action),
}
if len(r.Ports) != 0 {
portsCopy := r.Ports[:]
rule.Ports = &portsCopy
}
for _, gid := range r.Sources {
_, ok := cache[gid]
@ -314,13 +333,13 @@ func toPolicyResponse(account *server.Account, policy *server.Policy) *api.Polic
return ap
}
func groupMinimumsToStrings(account *server.Account, gm []api.GroupMinimum) []string {
func groupMinimumsToStrings(account *server.Account, gm []string) []string {
result := make([]string, 0, len(gm))
for _, gm := range gm {
if _, ok := account.Groups[gm.Id]; ok {
for _, g := range gm {
if _, ok := account.Groups[g]; !ok {
continue
}
result = append(result, gm.Id)
result = append(result, g)
}
return result
}

View File

@ -0,0 +1,315 @@
package http
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/status"
"github.com/gorilla/mux"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/magiconair/properties/assert"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/mock_server"
)
func initPoliciesTestData(policies ...*server.Policy) *Policies {
testPolicies := make(map[string]*server.Policy, len(policies))
for _, policy := range policies {
testPolicies[policy.ID] = policy
}
return &Policies{
accountManager: &mock_server.MockAccountManager{
GetPolicyFunc: func(_, policyID, _ string) (*server.Policy, error) {
policy, ok := testPolicies[policyID]
if !ok {
return nil, status.Errorf(status.NotFound, "policy not found")
}
return policy, nil
},
SavePolicyFunc: func(_, _ string, policy *server.Policy) error {
if !strings.HasPrefix(policy.ID, "id-") {
policy.ID = "id-was-set"
policy.Rules[0].ID = "id-was-set"
}
return nil
},
SaveRuleFunc: func(_, _ string, rule *server.Rule) error {
if !strings.HasPrefix(rule.ID, "id-") {
rule.ID = "id-was-set"
}
return nil
},
GetRuleFunc: func(_, ruleID, _ string) (*server.Rule, error) {
if ruleID != "idoftherule" {
return nil, fmt.Errorf("not found")
}
return &server.Rule{
ID: "idoftherule",
Name: "Rule",
Source: []string{"idofsrcrule"},
Destination: []string{"idofdestrule"},
Flow: server.TrafficFlowBidirect,
}, nil
},
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
user := server.NewAdminUser("test_user")
return &server.Account{
Id: claims.AccountId,
Domain: "hotmail.com",
Policies: []*server.Policy{
{ID: "id-existed"},
},
Groups: map[string]*server.Group{
"F": {ID: "F"},
"G": {ID: "G"},
},
Users: map[string]*server.User{
"test_user": user,
},
}, user, nil
},
},
claimsExtractor: jwtclaims.NewClaimsExtractor(
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
return jwtclaims.AuthorizationClaims{
UserId: "test_user",
Domain: "hotmail.com",
AccountId: "test_id",
}
}),
),
}
}
func TestPoliciesGetPolicy(t *testing.T) {
tt := []struct {
name string
expectedStatus int
expectedBody bool
requestType string
requestPath string
requestBody io.Reader
}{
{
name: "GetPolicy OK",
expectedBody: true,
requestType: http.MethodGet,
requestPath: "/api/policies/idofthepolicy",
expectedStatus: http.StatusOK,
},
{
name: "GetPolicy not found",
requestType: http.MethodGet,
requestPath: "/api/policies/notexists",
expectedStatus: http.StatusNotFound,
},
}
policy := &server.Policy{
ID: "idofthepolicy",
Name: "Rule",
Rules: []*server.PolicyRule{
{ID: "idoftherule", Name: "Rule"},
},
}
p := initPoliciesTestData(policy)
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
recorder := httptest.NewRecorder()
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
router := mux.NewRouter()
router.HandleFunc("/api/policies/{policyId}", p.GetPolicy).Methods("GET")
router.ServeHTTP(recorder, req)
res := recorder.Result()
defer res.Body.Close()
if status := recorder.Code; status != tc.expectedStatus {
t.Errorf("handler returned wrong status code: got %v want %v",
status, tc.expectedStatus)
return
}
if !tc.expectedBody {
return
}
content, err := io.ReadAll(res.Body)
if err != nil {
t.Fatalf("I don't know what I expected; %v", err)
}
var got api.Policy
if err = json.Unmarshal(content, &got); err != nil {
t.Fatalf("Sent content is not in correct json format; %v", err)
}
assert.Equal(t, *got.Id, policy.ID)
assert.Equal(t, got.Name, policy.Name)
})
}
}
func TestPoliciesWritePolicy(t *testing.T) {
str := func(s string) *string { return &s }
tt := []struct {
name string
expectedStatus int
expectedBody bool
expectedPolicy *api.Policy
requestType string
requestPath string
requestBody io.Reader
}{
{
name: "WritePolicy POST OK",
requestType: http.MethodPost,
requestPath: "/api/policies",
requestBody: bytes.NewBuffer(
[]byte(`{
"Name":"Default POSTed Policy",
"Rules":[
{
"Name":"Default POSTed Policy",
"Description": "Description",
"Protocol": "tcp",
"Action": "accept",
"Bidirectional":true
}
]}`)),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedPolicy: &api.Policy{
Id: str("id-was-set"),
Name: "Default POSTed Policy",
Rules: []api.PolicyRule{
{
Id: str("id-was-set"),
Name: "Default POSTed Policy",
Description: str("Description"),
Protocol: "tcp",
Action: "accept",
Bidirectional: true,
},
},
},
},
{
name: "WritePolicy POST Invalid Name",
requestType: http.MethodPost,
requestPath: "/api/policies",
requestBody: bytes.NewBuffer(
[]byte(`{"Name":""}`)),
expectedStatus: http.StatusUnprocessableEntity,
expectedBody: false,
},
{
name: "WritePolicy PUT OK",
requestType: http.MethodPut,
requestPath: "/api/policies/id-existed",
requestBody: bytes.NewBuffer(
[]byte(`{
"ID": "id-existed",
"Name":"Default POSTed Policy",
"Rules":[
{
"ID": "id-existed",
"Name":"Default POSTed Policy",
"Description": "Description",
"Protocol": "tcp",
"Action": "accept",
"Bidirectional":true
}
]}`)),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedPolicy: &api.Policy{
Id: str("id-existed"),
Name: "Default POSTed Policy",
Rules: []api.PolicyRule{
{
Id: str("id-existed"),
Name: "Default POSTed Policy",
Description: str("Description"),
Protocol: "tcp",
Action: "accept",
Bidirectional: true,
},
},
},
},
{
name: "WritePolicy PUT Invalid Name",
requestType: http.MethodPut,
requestPath: "/api/policies/id-existed",
requestBody: bytes.NewBuffer(
[]byte(`{"ID":"id-existed","Name":"","Rules":[{"ID":"id-existed"}]}`)),
expectedStatus: http.StatusUnprocessableEntity,
},
}
p := initPoliciesTestData(&server.Policy{
ID: "id-existed",
Name: "Default POSTed Rule",
Rules: []*server.PolicyRule{
{
ID: "id-existed",
Name: "Default POSTed Rule",
Bidirectional: true,
},
},
})
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
recorder := httptest.NewRecorder()
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
router := mux.NewRouter()
router.HandleFunc("/api/policies", p.CreatePolicy).Methods("POST")
router.HandleFunc("/api/policies/{policyId}", p.UpdatePolicy).Methods("PUT")
router.ServeHTTP(recorder, req)
res := recorder.Result()
defer res.Body.Close()
content, err := io.ReadAll(res.Body)
if err != nil {
t.Fatalf("I don't know what I expected; %v", err)
return
}
if status := recorder.Code; status != tc.expectedStatus {
t.Errorf("handler returned wrong status code: got %v want %v, content: %s",
status, tc.expectedStatus, string(content))
return
}
if !tc.expectedBody {
return
}
expected, err := json.Marshal(tc.expectedPolicy)
if err != nil {
t.Fatalf("marshal expected policy: %v", err)
return
}
assert.Equal(t, strings.Trim(string(content), " \n"), string(expected), "content mismatch")
})
}
}

View File

@ -112,10 +112,6 @@ func (h *RulesHandler) UpdateRule(w http.ResponseWriter, r *http.Request) {
policy.Rules[0].Destinations = reqDestinations
policy.Rules[0].Enabled = !req.Disabled
policy.Rules[0].Description = req.Description
if err := policy.UpdateQueryFromRules(); err != nil {
util.WriteError(err, w)
return
}
switch req.Flow {
case server.TrafficFlowBidirectString:

View File

@ -30,9 +30,6 @@ func initRulesTestData(rules ...*server.Rule) *RulesHandler {
if err != nil {
panic(err)
}
if err := policy.UpdateQueryFromRules(); err != nil {
panic(err)
}
testPolicies[policy.ID] = policy
}
return &RulesHandler{

View File

@ -25,11 +25,12 @@ const (
)
type NetworkMap struct {
Peers []*Peer
Network *Network
Routes []*route.Route
DNSConfig nbdns.Config
OfflinePeers []*Peer
Peers []*Peer
Network *Network
Routes []*route.Route
DNSConfig nbdns.Config
OfflinePeers []*Peer
FirewallRules []*FirewallRule
}
type Network struct {

View File

@ -225,8 +225,7 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er
// fetch all the peers that have access to the user's peers
for _, peer := range peers {
// TODO: use firewall rules
aclPeers := account.getPeersByACL(peer.ID)
aclPeers, _ := account.getPeerConnectionResources(peer.ID)
for _, p := range aclPeers {
peersMap[p.ID] = p
}
@ -865,7 +864,7 @@ func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*Pee
}
for _, p := range userPeers {
aclPeers := account.getPeersByACL(p.ID)
aclPeers, _ := account.getPeerConnectionResources(p.ID)
for _, aclPeer := range aclPeers {
if aclPeer.ID == peerID {
return peer, nil
@ -884,98 +883,6 @@ func updatePeerMeta(peer *Peer, meta PeerSystemMeta, account *Account) (*Peer, b
return peer, false
}
// GetPeerRules returns a list of source or destination rules of a given peer.
func (a *Account) GetPeerRules(peerID string) (srcRules []*Rule, dstRules []*Rule) {
// Rules are group based so there is no direct access to peers.
// First, find all groups that the given peer belongs to
peerGroups := make(map[string]struct{})
for s, group := range a.Groups {
for _, peer := range group.Peers {
if peerID == peer {
peerGroups[s] = struct{}{}
break
}
}
}
// Second, find all rules that have discovered source and destination groups
srcRulesMap := make(map[string]*Rule)
dstRulesMap := make(map[string]*Rule)
for _, rule := range a.Rules {
for _, g := range rule.Source {
if _, ok := peerGroups[g]; ok && srcRulesMap[rule.ID] == nil {
srcRules = append(srcRules, rule)
srcRulesMap[rule.ID] = rule
}
}
for _, g := range rule.Destination {
if _, ok := peerGroups[g]; ok && dstRulesMap[rule.ID] == nil {
dstRules = append(dstRules, rule)
dstRulesMap[rule.ID] = rule
}
}
}
return srcRules, dstRules
}
// getPeersByACL returns all peers that given peer has access to.
func (a *Account) getPeersByACL(peerID string) []*Peer {
var peers []*Peer
srcRules, dstRules := a.GetPeerRules(peerID)
groups := map[string]*Group{}
for _, r := range srcRules {
if r.Disabled {
continue
}
if r.Flow == TrafficFlowBidirect {
for _, gid := range r.Destination {
if group, ok := a.Groups[gid]; ok {
groups[gid] = group
}
}
}
}
for _, r := range dstRules {
if r.Disabled {
continue
}
if r.Flow == TrafficFlowBidirect {
for _, gid := range r.Source {
if group, ok := a.Groups[gid]; ok {
groups[gid] = group
}
}
}
}
peersSet := make(map[string]struct{})
for _, g := range groups {
for _, pid := range g.Peers {
peer, ok := a.Peers[pid]
if !ok {
log.Warnf(
"peer %s found in group %s but doesn't belong to account %s",
pid,
g.ID,
a.Id,
)
continue
}
// exclude original peer
if _, ok := peersSet[peer.ID]; peer.ID != peerID && !ok {
peersSet[peer.ID] = struct{}{}
peers = append(peers, peer.Copy())
}
}
}
return peers
}
// updateAccountPeers updates all peers that belong to an account.
// Should be called when changes have to be synced to peers.
func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {

View File

@ -227,16 +227,13 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
policy.Enabled = true
policy.Rules = []*PolicyRule{
{
Enabled: true,
Sources: []string{group1.ID},
Destinations: []string{group2.ID},
Action: PolicyTrafficActionAccept,
Enabled: true,
Sources: []string{group1.ID},
Destinations: []string{group2.ID},
Bidirectional: true,
Action: PolicyTrafficActionAccept,
},
}
if err := policy.UpdateQueryFromRules(); err != nil {
t.Errorf("expecting policy to be updated, got failure %v", err)
return
}
err = manager.SavePolicy(account.Id, userID, &policy)
if err != nil {
t.Errorf("expecting rule to be added, got failure %v", err)

View File

@ -1,18 +1,13 @@
package server
import (
"bytes"
"context"
_ "embed"
"fmt"
"html/template"
"strings"
"github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"github.com/open-policy-agent/opa/rego"
log "github.com/sirupsen/logrus"
)
// PolicyUpdateOperationType operation type
@ -21,6 +16,12 @@ type PolicyUpdateOperationType int
// PolicyTrafficActionType action type for the firewall
type PolicyTrafficActionType string
// PolicyRuleProtocolType type of traffic
type PolicyRuleProtocolType string
// PolicyRuleDirection direction of traffic
type PolicyRuleDirection string
const (
// PolicyTrafficActionAccept indicates that the traffic is accepted
PolicyTrafficActionAccept = PolicyTrafficActionType("accept")
@ -28,21 +29,35 @@ const (
PolicyTrafficActionDrop = PolicyTrafficActionType("drop")
)
const (
// PolicyRuleProtocolALL type of traffic
PolicyRuleProtocolALL = PolicyRuleProtocolType("all")
// PolicyRuleProtocolTCP type of traffic
PolicyRuleProtocolTCP = PolicyRuleProtocolType("tcp")
// PolicyRuleProtocolUDP type of traffic
PolicyRuleProtocolUDP = PolicyRuleProtocolType("udp")
// PolicyRuleProtocolICMP type of traffic
PolicyRuleProtocolICMP = PolicyRuleProtocolType("icmp")
)
const (
// PolicyRuleFlowDirect allows trafic from source to destination
PolicyRuleFlowDirect = PolicyRuleDirection("direct")
// PolicyRuleFlowBidirect allows traffic to both directions
PolicyRuleFlowBidirect = PolicyRuleDirection("bidirect")
)
const (
firewallRuleDirectionIN = 0
firewallRuleDirectionOUT = 1
)
// PolicyUpdateOperation operation object with type and values to be applied
type PolicyUpdateOperation struct {
Type PolicyUpdateOperationType
Values []string
}
//go:embed rego/default_policy_module.rego
var defaultPolicyModule string
//go:embed rego/default_policy.rego
var defaultPolicyText string
// defaultPolicyTemplate is a template for the default policy
var defaultPolicyTemplate = template.Must(template.New("policy").Parse(defaultPolicyText))
// PolicyRule is the metadata of the policy
type PolicyRule struct {
// ID of the policy rule
@ -65,18 +80,30 @@ type PolicyRule struct {
// Sources policy source groups
Sources []string
// Bidirectional define if the rule is applicable in both directions, sources, and destinations
Bidirectional bool
// Protocol type of the traffic
Protocol PolicyRuleProtocolType
// Ports or it ranges list
Ports []string
}
// Copy returns a copy of a policy rule
func (pm *PolicyRule) Copy() *PolicyRule {
return &PolicyRule{
ID: pm.ID,
Name: pm.Name,
Description: pm.Description,
Enabled: pm.Enabled,
Action: pm.Action,
Destinations: pm.Destinations[:],
Sources: pm.Sources[:],
ID: pm.ID,
Name: pm.Name,
Description: pm.Description,
Enabled: pm.Enabled,
Action: pm.Action,
Destinations: pm.Destinations[:],
Sources: pm.Sources[:],
Bidirectional: pm.Bidirectional,
Protocol: pm.Protocol,
Ports: pm.Ports[:],
}
}
@ -107,9 +134,6 @@ type Policy struct {
// Enabled status of the policy
Enabled bool
// Query of Rego the policy
Query string
// Rules of the policy
Rules []*PolicyRule
}
@ -121,7 +145,6 @@ func (p *Policy) Copy() *Policy {
Name: p.Name,
Description: p.Description,
Enabled: p.Enabled,
Query: p.Query,
}
for _, r := range p.Rules {
c.Rules = append(c.Rules, r.Copy())
@ -134,214 +157,124 @@ func (p *Policy) EventMeta() map[string]any {
return map[string]any{"name": p.Name}
}
// UpdateQueryFromRules marshals policy rules to Rego string and set it to Query
func (p *Policy) UpdateQueryFromRules() error {
type templateVars struct {
All []string
Source []string
Destination []string
}
queries := []string{}
// UpgradeAndFix different version of policies to latest version
func (p *Policy) UpgradeAndFix() {
for _, r := range p.Rules {
if !r.Enabled {
continue
// start migrate from version v0.20.3
if r.Protocol == "" {
r.Protocol = PolicyRuleProtocolALL
}
buff := new(bytes.Buffer)
input := templateVars{
All: append(r.Destinations[:], r.Sources...),
Source: r.Sources,
Destination: r.Destinations,
if r.Protocol == PolicyRuleProtocolALL && !r.Bidirectional {
r.Bidirectional = true
}
if err := defaultPolicyTemplate.Execute(buff, input); err != nil {
return status.Errorf(status.BadRequest, "failed to update policy query: %v", err)
}
queries = append(queries, buff.String())
// -- v0.20.4
}
p.Query = strings.Join(queries, "\n")
return nil
}
// FirewallRule is a rule of the firewall.
type FirewallRule struct {
// PeerID of the peer
PeerID string
// PeerIP of the peer
PeerIP string
// Direction of the traffic
Direction string
Direction int
// Action of the traffic
Action string
// Protocol of the traffic
Protocol string
// Port of the traffic
Port string
// id for internal purposes
id string
}
// parseFromRegoResult parses the Rego result to a FirewallRule.
func (f *FirewallRule) parseFromRegoResult(value interface{}) error {
object, ok := value.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid Rego query eval result")
}
// getPeerConnectionResources for a given peer
//
// This function returns the list of peers and firewall rules that are applicable to a given peer.
func (a *Account) getPeerConnectionResources(peerID string) ([]*Peer, []*FirewallRule) {
generateResources, getAccumulatedResources := a.connResourcesGenerator()
peerID, ok := object["ID"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer ID type")
}
peerIP, ok := object["IP"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer IP type")
}
direction, ok := object["Direction"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer direction type")
}
action, ok := object["Action"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer action type")
}
port, ok := object["Port"].(string)
if !ok {
return fmt.Errorf("invalid Rego query eval result peer port type")
}
f.PeerID = peerID
f.PeerIP = peerIP
f.Direction = direction
f.Action = action
f.Port = port
// NOTE: update this id each time when new field added
f.id = peerID + peerIP + direction + action + port
return nil
}
// queryPeersAndFwRulesByRego returns a list associated Peers and firewall rules list for this peer.
func (a *Account) queryPeersAndFwRulesByRego(
peerID string,
queryNumber int,
query string,
) ([]*Peer, []*FirewallRule) {
input := map[string]interface{}{
"peer_id": peerID,
"peers": a.Peers,
"groups": a.Groups,
}
stmt, err := rego.New(
rego.Query("data.netbird.all"),
rego.Module("netbird", defaultPolicyModule),
rego.Module(fmt.Sprintf("netbird-%d", queryNumber), query),
).PrepareForEval(context.TODO())
if err != nil {
log.WithError(err).Error("get Rego query")
return nil, nil
}
evalResult, err := stmt.Eval(
context.TODO(),
rego.EvalInput(input),
)
if err != nil {
log.WithError(err).Error("eval Rego query")
return nil, nil
}
if len(evalResult) == 0 || len(evalResult[0].Expressions) == 0 {
log.Trace("empty Rego query eval result")
return nil, nil
}
expressions, ok := evalResult[0].Expressions[0].Value.([]interface{})
if !ok {
return nil, nil
}
dst := make(map[string]struct{})
src := make(map[string]struct{})
peers := make([]*Peer, 0, len(expressions))
rules := make([]*FirewallRule, 0, len(expressions))
for _, v := range expressions {
rule := &FirewallRule{}
if err := rule.parseFromRegoResult(v); err != nil {
log.WithError(err).Error("parse Rego query eval result")
continue
}
rules = append(rules, rule)
switch rule.Direction {
case "dst":
if _, ok := dst[rule.PeerID]; ok {
continue
}
dst[rule.PeerID] = struct{}{}
case "src":
if _, ok := src[rule.PeerID]; ok {
continue
}
src[rule.PeerID] = struct{}{}
default:
log.WithField("direction", rule.Direction).Error("invalid direction")
continue
}
}
added := make(map[string]struct{})
if _, ok := src[peerID]; ok {
for id := range dst {
if _, ok := added[id]; !ok && id != peerID {
added[id] = struct{}{}
}
}
}
if _, ok := dst[peerID]; ok {
for id := range src {
if _, ok := added[id]; !ok && id != peerID {
added[id] = struct{}{}
}
}
}
for id := range added {
peers = append(peers, a.Peers[id])
}
return peers, rules
}
// getPeersByPolicy returns all peers that given peer has access to.
func (a *Account) getPeersByPolicy(peerID string) (peers []*Peer, rules []*FirewallRule) {
peersSeen := make(map[string]struct{})
ruleSeen := make(map[string]struct{})
for i, policy := range a.Policies {
for _, policy := range a.Policies {
if !policy.Enabled {
continue
}
p, r := a.queryPeersAndFwRulesByRego(peerID, i, policy.Query)
for _, peer := range p {
if _, ok := peersSeen[peer.ID]; ok {
for _, rule := range policy.Rules {
if !rule.Enabled {
continue
}
peers = append(peers, peer)
peersSeen[peer.ID] = struct{}{}
}
for _, rule := range r {
if _, ok := ruleSeen[rule.id]; ok {
continue
sourcePeers, peerInSources := getAllPeersFromGroups(a, rule.Sources, peerID)
destinationPeers, peerInDestinations := getAllPeersFromGroups(a, rule.Destinations, peerID)
if rule.Bidirectional {
if peerInSources {
generateResources(rule, destinationPeers, firewallRuleDirectionIN)
}
if peerInDestinations {
generateResources(rule, sourcePeers, firewallRuleDirectionOUT)
}
}
if peerInSources {
generateResources(rule, destinationPeers, firewallRuleDirectionOUT)
}
if peerInDestinations {
generateResources(rule, sourcePeers, firewallRuleDirectionIN)
}
rules = append(rules, rule)
ruleSeen[rule.id] = struct{}{}
}
}
return
return getAccumulatedResources()
}
// connResourcesGenerator returns generator and accumulator function which returns the result of generator calls
//
// The generator function is used to generate the list of peers and firewall rules that are applicable to a given peer.
// It safe to call the generator function multiple times for same peer and different rules no duplicates will be
// generated. The accumulator function returns the result of all the generator calls.
func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), func() ([]*Peer, []*FirewallRule)) {
rulesExists := make(map[string]struct{})
peersExists := make(map[string]struct{})
rules := make([]*FirewallRule, 0)
peers := make([]*Peer, 0)
return func(rule *PolicyRule, groupPeers []*Peer, direction int) {
for _, peer := range groupPeers {
if _, ok := peersExists[peer.ID]; !ok {
peers = append(peers, peer)
peersExists[peer.ID] = struct{}{}
}
fwRule := FirewallRule{
PeerIP: peer.IP.String(),
Direction: direction,
Action: string(rule.Action),
Protocol: string(rule.Protocol),
}
ruleID := fmt.Sprintf("%s%d", peer.ID+peer.IP.String(), direction)
ruleID += string(rule.Protocol) + string(rule.Action) + strings.Join(rule.Ports, ",")
if _, ok := rulesExists[ruleID]; ok {
continue
}
rulesExists[ruleID] = struct{}{}
if len(rule.Ports) == 0 {
rules = append(rules, &fwRule)
continue
}
for _, port := range rule.Ports {
addRule := fwRule
addRule.Port = port
rules = append(rules, &addRule)
}
}
}, func() ([]*Peer, []*FirewallRule) {
return peers, rules
}
}
// GetPolicy from the store
@ -475,3 +408,63 @@ func (am *DefaultAccountManager) savePolicy(account *Account, policy *Policy) (e
}
return
}
func toProtocolFirewallRules(update []*FirewallRule) []*proto.FirewallRule {
result := make([]*proto.FirewallRule, len(update))
for i := range update {
direction := proto.FirewallRule_IN
if update[i].Direction == firewallRuleDirectionOUT {
direction = proto.FirewallRule_OUT
}
action := proto.FirewallRule_ACCEPT
if update[i].Action == string(PolicyTrafficActionDrop) {
action = proto.FirewallRule_DROP
}
protocol := proto.FirewallRule_UNKNOWN
switch PolicyRuleProtocolType(update[i].Protocol) {
case PolicyRuleProtocolALL:
protocol = proto.FirewallRule_ALL
case PolicyRuleProtocolTCP:
protocol = proto.FirewallRule_TCP
case PolicyRuleProtocolUDP:
protocol = proto.FirewallRule_UDP
case PolicyRuleProtocolICMP:
protocol = proto.FirewallRule_ICMP
}
result[i] = &proto.FirewallRule{
PeerIP: update[i].PeerIP,
Direction: direction,
Action: action,
Protocol: protocol,
Port: update[i].Port,
}
}
return result
}
// getAllPeersFromGroups for given peer ID and list of groups
//
// Returns list of peers and boolean indicating if peer is in any of the groups
func getAllPeersFromGroups(account *Account, groups []string, peerID string) ([]*Peer, bool) {
peerInGroups := false
filteredPeers := make([]*Peer, 0, len(groups))
for _, g := range groups {
group, ok := account.Groups[g]
if !ok {
continue
}
for _, p := range group.Peers {
peer := account.Peers[p]
if peer.ID == peerID {
peerInGroups = true
continue
}
filteredPeers = append(filteredPeers, peer)
}
}
return filteredPeers, peerInGroups
}

View File

@ -1,6 +1,7 @@
package server
import (
"fmt"
"net"
"testing"
@ -11,262 +12,412 @@ import (
func TestAccount_getPeersByPolicy(t *testing.T) {
account := &Account{
Peers: map[string]*Peer{
"cfif97at2r9s73au3q00": {
ID: "cfif97at2r9s73au3q00",
"peerA": {
ID: "peerA",
IP: net.ParseIP("100.65.14.88"),
},
"cfif97at2r9s73au3q0g": {
ID: "cfif97at2r9s73au3q0g",
"peerB": {
ID: "peerB",
IP: net.ParseIP("100.65.80.39"),
},
"cfif97at2r9s73au3q10": {
ID: "cfif97at2r9s73au3q10",
"peerC": {
ID: "peerC",
IP: net.ParseIP("100.65.254.139"),
},
"cfif97at2r9s73au3q20": {
ID: "cfif97at2r9s73au3q20",
"peerD": {
ID: "peerD",
IP: net.ParseIP("100.65.62.5"),
},
"cfj4tiqt2r9s73dmeun0": {
ID: "cfj4tiqt2r9s73dmeun0",
"peerE": {
ID: "peerE",
IP: net.ParseIP("100.65.32.206"),
},
"cg7h032t2r9s73cg5fk0": {
ID: "cg7h032t2r9s73cg5fk0",
"peerF": {
ID: "peerF",
IP: net.ParseIP("100.65.250.202"),
},
"cgcnkj2t2r9s73cg5vv0": {
ID: "cgcnkj2t2r9s73cg5vv0",
"peerG": {
ID: "peerG",
IP: net.ParseIP("100.65.13.186"),
},
"cgcol4qt2r9s73cg601g": {
ID: "cgcol4qt2r9s73cg601g",
"peerH": {
ID: "peerH",
IP: net.ParseIP("100.65.29.55"),
},
},
Groups: map[string]*Group{
"cet9e92t2r9s7383ns20": {
ID: "cet9e92t2r9s7383ns20",
"GroupAll": {
ID: "GroupAll",
Name: "All",
Peers: []string{
"cfif97at2r9s73au3q0g",
"cfif97at2r9s73au3q00",
"cfif97at2r9s73au3q20",
"cfif97at2r9s73au3q10",
"cfj4tiqt2r9s73dmeun0",
"cg7h032t2r9s73cg5fk0",
"cgcnkj2t2r9s73cg5vv0",
"cgcol4qt2r9s73cg601g",
"peerB",
"peerA",
"peerD",
"peerC",
"peerE",
"peerF",
"peerG",
"peerH",
},
},
"cev90bat2r9s7383o150": {
ID: "cev90bat2r9s7383o150",
"GroupSwarm": {
ID: "GroupSwarm",
Name: "swarm",
Peers: []string{
"cfif97at2r9s73au3q0g",
"cfif97at2r9s73au3q00",
"cfif97at2r9s73au3q20",
"cfj4tiqt2r9s73dmeun0",
"cgcnkj2t2r9s73cg5vv0",
"cgcol4qt2r9s73cg601g",
"peerB",
"peerA",
"peerD",
"peerE",
"peerG",
"peerH",
},
},
},
Rules: map[string]*Rule{
"cet9e92t2r9s7383ns2g": {
ID: "cet9e92t2r9s7383ns2g",
"RuleDefault": {
ID: "RuleDefault",
Name: "Default",
Description: "This is a default rule that allows connections between all the resources",
Source: []string{
"cet9e92t2r9s7383ns20",
"GroupAll",
},
Destination: []string{
"cet9e92t2r9s7383ns20",
"GroupAll",
},
},
"cev90bat2r9s7383o15g": {
ID: "cev90bat2r9s7383o15g",
"RuleSwarm": {
ID: "RuleSwarm",
Name: "Swarm",
Description: "",
Source: []string{
"cev90bat2r9s7383o150",
"cet9e92t2r9s7383ns20",
"GroupSwarm",
"GroupAll",
},
Destination: []string{
"cev90bat2r9s7383o150",
"GroupSwarm",
},
},
},
}
rule1, err := RuleToPolicy(account.Rules["cet9e92t2r9s7383ns2g"])
rule1, err := RuleToPolicy(account.Rules["RuleDefault"])
assert.NoError(t, err)
rule2, err := RuleToPolicy(account.Rules["cev90bat2r9s7383o15g"])
rule2, err := RuleToPolicy(account.Rules["RuleSwarm"])
assert.NoError(t, err)
account.Policies = append(account.Policies, rule1, rule2)
t.Run("check that all peers get map", func(t *testing.T) {
for _, p := range account.Peers {
peers, firewallRules := account.getPeersByPolicy(p.ID)
peers, firewallRules := account.getPeerConnectionResources(p.ID)
assert.GreaterOrEqual(t, len(peers), 2, "mininum number peers should present")
assert.GreaterOrEqual(t, len(firewallRules), 2, "mininum number of firewall rules should present")
}
})
t.Run("check first peer map details", func(t *testing.T) {
peers, firewallRules := account.getPeersByPolicy("cfif97at2r9s73au3q0g")
peers, firewallRules := account.getPeerConnectionResources("peerB")
assert.Len(t, peers, 7)
assert.Contains(t, peers, account.Peers["cfif97at2r9s73au3q00"])
assert.Contains(t, peers, account.Peers["cfif97at2r9s73au3q10"])
assert.Contains(t, peers, account.Peers["cfif97at2r9s73au3q20"])
assert.Contains(t, peers, account.Peers["cfj4tiqt2r9s73dmeun0"])
assert.Contains(t, peers, account.Peers["cg7h032t2r9s73cg5fk0"])
assert.Contains(t, peers, account.Peers["peerA"])
assert.Contains(t, peers, account.Peers["peerC"])
assert.Contains(t, peers, account.Peers["peerD"])
assert.Contains(t, peers, account.Peers["peerE"])
assert.Contains(t, peers, account.Peers["peerF"])
epectedFirewallRules := []*FirewallRule{
{
PeerID: "cfif97at2r9s73au3q00",
PeerIP: "100.65.14.88",
Direction: "src",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
id: "cfif97at2r9s73au3q00100.65.14.88srcaccept",
},
{
PeerID: "cfif97at2r9s73au3q00",
PeerIP: "100.65.14.88",
Direction: "dst",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
id: "cfif97at2r9s73au3q00100.65.14.88dstaccept",
},
{
PeerID: "cfif97at2r9s73au3q0g",
PeerIP: "100.65.80.39",
Direction: "dst",
Action: "accept",
Port: "",
id: "cfif97at2r9s73au3q0g100.65.80.39dstaccept",
},
{
PeerID: "cfif97at2r9s73au3q0g",
PeerIP: "100.65.80.39",
Direction: "src",
Action: "accept",
Port: "",
id: "cfif97at2r9s73au3q0g100.65.80.39srcaccept",
},
{
PeerID: "cfif97at2r9s73au3q10",
PeerIP: "100.65.254.139",
Direction: "dst",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
id: "cfif97at2r9s73au3q10100.65.254.139dstaccept",
},
{
PeerID: "cfif97at2r9s73au3q10",
PeerIP: "100.65.254.139",
Direction: "src",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
id: "cfif97at2r9s73au3q10100.65.254.139srcaccept",
},
{
PeerID: "cfif97at2r9s73au3q20",
PeerIP: "100.65.62.5",
Direction: "dst",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
id: "cfif97at2r9s73au3q20100.65.62.5dstaccept",
},
{
PeerID: "cfif97at2r9s73au3q20",
PeerIP: "100.65.62.5",
Direction: "src",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
id: "cfif97at2r9s73au3q20100.65.62.5srcaccept",
},
{
PeerID: "cfj4tiqt2r9s73dmeun0",
PeerIP: "100.65.32.206",
Direction: "dst",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
id: "cfj4tiqt2r9s73dmeun0100.65.32.206dstaccept",
},
{
PeerID: "cfj4tiqt2r9s73dmeun0",
PeerIP: "100.65.32.206",
Direction: "src",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
id: "cfj4tiqt2r9s73dmeun0100.65.32.206srcaccept",
},
{
PeerID: "cg7h032t2r9s73cg5fk0",
PeerIP: "100.65.250.202",
Direction: "dst",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
id: "cg7h032t2r9s73cg5fk0100.65.250.202dstaccept",
},
{
PeerID: "cg7h032t2r9s73cg5fk0",
PeerIP: "100.65.250.202",
Direction: "src",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
id: "cg7h032t2r9s73cg5fk0100.65.250.202srcaccept",
},
{
PeerID: "cgcnkj2t2r9s73cg5vv0",
PeerIP: "100.65.13.186",
Direction: "dst",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
id: "cgcnkj2t2r9s73cg5vv0100.65.13.186dstaccept",
},
{
PeerID: "cgcnkj2t2r9s73cg5vv0",
PeerIP: "100.65.13.186",
Direction: "src",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
id: "cgcnkj2t2r9s73cg5vv0100.65.13.186srcaccept",
},
{
PeerID: "cgcol4qt2r9s73cg601g",
PeerIP: "100.65.29.55",
Direction: "dst",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
id: "cgcol4qt2r9s73cg601g100.65.29.55dstaccept",
},
{
PeerID: "cgcol4qt2r9s73cg601g",
PeerIP: "100.65.29.55",
Direction: "src",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
id: "cgcol4qt2r9s73cg601g100.65.29.55srcaccept",
},
}
assert.Len(t, firewallRules, len(epectedFirewallRules))
slices.SortFunc(firewallRules, func(a, b *FirewallRule) bool {
return a.PeerID < b.PeerID
})
slices.SortFunc(epectedFirewallRules, sortFunc())
slices.SortFunc(firewallRules, sortFunc())
for i := range firewallRules {
assert.Equal(t, epectedFirewallRules[i], firewallRules[i])
}
})
}
func TestAccount_getPeersByPolicyDirect(t *testing.T) {
account := &Account{
Peers: map[string]*Peer{
"peerA": {
ID: "peerA",
IP: net.ParseIP("100.65.14.88"),
},
"peerB": {
ID: "peerB",
IP: net.ParseIP("100.65.80.39"),
},
"peerC": {
ID: "peerC",
IP: net.ParseIP("100.65.254.139"),
},
},
Groups: map[string]*Group{
"GroupAll": {
ID: "GroupAll",
Name: "All",
Peers: []string{
"peerB",
"peerA",
"peerC",
},
},
"GroupSwarm": {
ID: "GroupSwarm",
Name: "swarm",
Peers: []string{
"peerB",
},
},
"peerF": {
ID: "peerF",
Name: "dmz",
Peers: []string{
"peerC",
},
},
},
Rules: map[string]*Rule{
"RuleDefault": {
ID: "RuleDefault",
Name: "Default",
Disabled: true,
Description: "This is a default rule that allows connections between all the resources",
Source: []string{
"GroupAll",
},
Destination: []string{
"GroupAll",
},
},
"RuleSwarm": {
ID: "RuleSwarm",
Name: "Swarm",
Description: "",
Source: []string{
"GroupSwarm",
},
Destination: []string{
"peerF",
},
},
},
}
rule1, err := RuleToPolicy(account.Rules["RuleDefault"])
assert.NoError(t, err)
rule2, err := RuleToPolicy(account.Rules["RuleSwarm"])
assert.NoError(t, err)
account.Policies = append(account.Policies, rule1, rule2)
t.Run("check first peer map", func(t *testing.T) {
peers, firewallRules := account.getPeerConnectionResources("peerB")
assert.Contains(t, peers, account.Peers["peerC"])
epectedFirewallRules := []*FirewallRule{
{
PeerIP: "100.65.254.139",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
},
{
PeerIP: "100.65.254.139",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
},
}
assert.Len(t, firewallRules, len(epectedFirewallRules))
slices.SortFunc(epectedFirewallRules, sortFunc())
slices.SortFunc(firewallRules, sortFunc())
for i := range firewallRules {
assert.Equal(t, epectedFirewallRules[i], firewallRules[i])
}
})
t.Run("check second peer map", func(t *testing.T) {
peers, firewallRules := account.getPeerConnectionResources("peerC")
assert.Contains(t, peers, account.Peers["peerB"])
epectedFirewallRules := []*FirewallRule{
{
PeerIP: "100.65.80.39",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
},
{
PeerIP: "100.65.80.39",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
},
}
assert.Len(t, firewallRules, len(epectedFirewallRules))
slices.SortFunc(epectedFirewallRules, sortFunc())
slices.SortFunc(firewallRules, sortFunc())
for i := range firewallRules {
assert.Equal(t, epectedFirewallRules[i], firewallRules[i])
}
})
account.Policies[1].Rules[0].Bidirectional = false
t.Run("check first peer map directional only", func(t *testing.T) {
peers, firewallRules := account.getPeerConnectionResources("peerB")
assert.Contains(t, peers, account.Peers["peerC"])
epectedFirewallRules := []*FirewallRule{
{
PeerIP: "100.65.254.139",
Direction: firewallRuleDirectionOUT,
Action: "accept",
Protocol: "all",
Port: "",
},
}
assert.Len(t, firewallRules, len(epectedFirewallRules))
slices.SortFunc(epectedFirewallRules, sortFunc())
slices.SortFunc(firewallRules, sortFunc())
for i := range firewallRules {
assert.Equal(t, epectedFirewallRules[i], firewallRules[i])
}
})
t.Run("check second peer map directional only", func(t *testing.T) {
peers, firewallRules := account.getPeerConnectionResources("peerC")
assert.Contains(t, peers, account.Peers["peerB"])
epectedFirewallRules := []*FirewallRule{
{
PeerIP: "100.65.80.39",
Direction: firewallRuleDirectionIN,
Action: "accept",
Protocol: "all",
Port: "",
},
}
assert.Len(t, firewallRules, len(epectedFirewallRules))
slices.SortFunc(epectedFirewallRules, sortFunc())
slices.SortFunc(firewallRules, sortFunc())
for i := range firewallRules {
assert.Equal(t, epectedFirewallRules[i], firewallRules[i])
}
})
}
func sortFunc() func(a *FirewallRule, b *FirewallRule) bool {
return func(a, b *FirewallRule) bool {
return a.PeerIP+fmt.Sprintf("%d", a.Direction) < b.PeerIP+fmt.Sprintf("%d", b.Direction)
}
}

View File

@ -1,9 +0,0 @@
package netbird
all[rule] {
is_peer_in_any_group([{{range $i, $e := .All}}{{if $i}},{{end}}"{{$e}}"{{end}}])
rule := {
{{range $i, $e := .Destination}}rules_from_group("{{$e}}", "dst", "accept", ""),{{end}}
{{range $i, $e := .Source}}rules_from_group("{{$e}}", "src", "accept", ""),{{end}}
}[_][_]
}

View File

@ -1,34 +0,0 @@
package netbird
import future.keywords.if
import future.keywords.in
import future.keywords.contains
# get_rule builds a netbird rule object from given parameters
get_rule(peer_id, direction, action, port) := rule if {
peer := input.peers[_]
peer.ID == peer_id
rule := {
"ID": peer.ID,
"IP": peer.IP,
"Direction": direction,
"Action": action,
"Port": port,
}
}
# netbird_rules_from_group returns a list of netbird rules for a given group_id
rules_from_group(group_id, direction, action, port) := rules if {
group := input.groups[_]
group.ID == group_id
rules := [get_rule(peer, direction, action, port) | peer := group.Peers[_]]
}
# is_peer_in_any_group checks that input peer present at least in one group
is_peer_in_any_group(groups) := count([group_id]) > 0 if {
group_id := groups[_]
group := input.groups[_]
group.ID == group_id
peer := group.Peers[_]
peer == input.peer_id
}

View File

@ -911,8 +911,6 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
newPolicy.Name = "peer1 only"
newPolicy.Rules[0].Sources = []string{newGroup.ID}
newPolicy.Rules[0].Destinations = []string{newGroup.ID}
err = newPolicy.UpdateQueryFromRules()
require.NoError(t, err)
err = am.SavePolicy(account.Id, userID, newPolicy)
require.NoError(t, err)

View File

@ -67,13 +67,15 @@ func (r *Rule) ToPolicyRule() *PolicyRule {
return nil
}
return &PolicyRule{
ID: r.ID,
Name: r.Name,
Enabled: !r.Disabled,
Description: r.Description,
Action: PolicyTrafficActionAccept,
Destinations: r.Destination,
Sources: r.Source,
ID: r.ID,
Name: r.Name,
Enabled: !r.Disabled,
Description: r.Description,
Destinations: r.Destination,
Sources: r.Source,
Bidirectional: true,
Protocol: PolicyRuleProtocolALL,
Action: PolicyTrafficActionAccept,
}
}
@ -82,15 +84,11 @@ func RuleToPolicy(rule *Rule) (*Policy, error) {
if rule == nil {
return nil, fmt.Errorf("rule is empty")
}
policy := &Policy{
return &Policy{
ID: rule.ID,
Name: rule.Name,
Description: rule.Description,
Enabled: !rule.Disabled,
Rules: []*PolicyRule{rule.ToPolicyRule()},
}
if err := policy.UpdateQueryFromRules(); err != nil {
return nil, err
}
return policy, nil
}, nil
}