Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions config/crowdsec-firewall-bouncer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,22 @@ iptables_chains:

## nftables
nftables:
ipv4:
enabled: true
enabled: true
targets:
- blacklist: crowdsec-blacklists
set-only: false
table: crowdsec
chain: crowdsec-chain
priority: -10
ipv6:
enabled: true
family: ip
protocol: ip
hook: input
- blacklist: crowdsec6-blacklists
set-only: false
table: crowdsec6
chain: crowdsec6-chain
priority: -10

nftables_hooks:
- input
- forward
family: ip6
protocol: ip6
hook: input

# packet filter
pf:
Expand Down
64 changes: 10 additions & 54 deletions pkg/cfg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"

"github.com/crowdsecurity/cs-firewall-bouncer/pkg/types"
"github.com/crowdsecurity/go-cs-lib/csstring"
"github.com/crowdsecurity/go-cs-lib/ptr"
"github.com/crowdsecurity/go-cs-lib/yamlpatch"
Expand All @@ -19,14 +20,6 @@ type PrometheusConfig struct {
ListenPort string `yaml:"listen_port"`
}

type nftablesFamilyConfig struct {
Enabled *bool `yaml:"enabled"`
SetOnly bool `yaml:"set-only"`
Table string `yaml:"table"`
Chain string `yaml:"chain"`
Priority int `yaml:"priority"`
}

const (
IpsetMode = "ipset"
IptablesMode = "iptables"
Expand All @@ -45,21 +38,21 @@ type BouncerConfig struct {
DenyAction string `yaml:"deny_action"`
DenyLog bool `yaml:"deny_log"`
DenyLogPrefix string `yaml:"deny_log_prefix"`
BlacklistsIpv4 string `yaml:"blacklists_ipv4"`
BlacklistsIpv6 string `yaml:"blacklists_ipv6"`
BlacklistsIpv4 string `yaml:"blacklists_ipv4"` // unused for nftables
BlacklistsIpv6 string `yaml:"blacklists_ipv6"` // unused for nftables
SetType string `yaml:"ipset_type"`
SetSize int `yaml:"ipset_size"`

// specific to iptables, following https://github.com/crowdsecurity/cs-firewall-bouncer/issues/19
IptablesChains []string `yaml:"iptables_chains"`
SupportedDecisionsTypes []string `yaml:"supported_decisions_types"`
// specific to nftables, following https://github.com/crowdsecurity/cs-firewall-bouncer/issues/74
// specific to nftables, following https://github.com/crowdsecurity/cs-firewall-bouncer/issues/74,
// https://github.com/crowdsecurity/cs-firewall-bouncer/issues/153
Nftables struct {
Ipv4 nftablesFamilyConfig `yaml:"ipv4"`
Ipv6 nftablesFamilyConfig `yaml:"ipv6"`
Enabled *bool `yaml:"enabled"`
Targets []types.NftablesTargetConfig `yaml:"targets"`
} `yaml:"nftables"`
NftablesHooks []string `yaml:"nftables_hooks"`
PF struct {
PF struct {
AnchorName string `yaml:"anchor_name"`
BatchSize int `yaml:"batch_size"`
} `yaml:"pf"`
Expand Down Expand Up @@ -157,45 +150,8 @@ func pfConfig(config *BouncerConfig) error {
}

func nftablesConfig(config *BouncerConfig) error {
// deal with defaults in a backward compatible way
if config.Nftables.Ipv4.Enabled == nil {
config.Nftables.Ipv4.Enabled = ptr.Of(true)
}

if config.Nftables.Ipv6.Enabled == nil {
if config.DisableIPV6 {
config.Nftables.Ipv4.Enabled = ptr.Of(false)
} else {
config.Nftables.Ipv6.Enabled = ptr.Of(true)
}
}

if *config.Nftables.Ipv4.Enabled {
if config.Nftables.Ipv4.Table == "" {
config.Nftables.Ipv4.Table = "crowdsec"
}

if config.Nftables.Ipv4.Chain == "" {
config.Nftables.Ipv4.Chain = "crowdsec-chain"
}
}

if *config.Nftables.Ipv6.Enabled {
if config.Nftables.Ipv6.Table == "" {
config.Nftables.Ipv6.Table = "crowdsec6"
}

if config.Nftables.Ipv6.Chain == "" {
config.Nftables.Ipv6.Chain = "crowdsec6-chain"
}
}

if !*config.Nftables.Ipv4.Enabled && !*config.Nftables.Ipv6.Enabled {
return fmt.Errorf("both IPv4 and IPv6 disabled, doing nothing")
}

if config.NftablesHooks == nil || len(config.NftablesHooks) == 0 {
config.NftablesHooks = []string{"input"}
if config.Nftables.Enabled == nil {
config.Nftables.Enabled = ptr.Of(false)
}

return nil
Expand Down
53 changes: 26 additions & 27 deletions pkg/nftables/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,11 @@ func (c *nftContext) collectDroppedPackets(path string, chain string) (int, int,
}

func (c *nftContext) ipFamily() string {
if c.version == "v4" {
return "ip"
}

return "ip6"
return c.version
}

func (c *nftContext) collectActiveBannedIPs(path string) (int, error) {
cmd := exec.Command(path, "-j", "list", "set", c.ipFamily(), c.tableName, c.blacklists)
cmd := exec.Command(path, "-j", "list", "set", c.ipFamily(), c.tableName, c.blacklist)

out, err := cmd.CombinedOutput()
if err != nil {
Expand All @@ -90,38 +86,33 @@ func (c *nftContext) collectActiveBannedIPs(path string) (int, error) {
return ret, nil
}

func (c *nftContext) collectDropped(path string, hooks []string) (int, int, int) {
func (c *nftContext) collectDropped(path string, droppedPackets *int, droppedBytes *int, banned *int) {
if c.conn == nil {
return 0, 0, 0
return
}

var droppedPackets, droppedBytes, banned int

if c.setOnly {
pkt, byt, err := c.collectDroppedPackets(path, c.chainName)
if err != nil {
log.Errorf("can't collect dropped packets for ip%s from nft: %s", c.version, err)
log.Errorf("can't collect dropped packets for %s from nft: %s", c.version, err)
}

droppedPackets += pkt
droppedBytes += byt
*droppedPackets += pkt
*droppedBytes += byt
} else {
for _, hook := range hooks {
pkt, byt, err := c.collectDroppedPackets(path, c.chainName+"-"+hook)
if err != nil {
log.Errorf("can't collect dropped packets for ip%s from nft: %s", c.version, err)
}
droppedPackets += pkt
droppedBytes += byt
pkt, byt, err := c.collectDroppedPackets(path, c.chainName+"-"+c.hook)
if err != nil {
log.Errorf("can't collect dropped packets for %s from nft: %s", c.version, err)
}
*droppedPackets += pkt
*droppedBytes += byt
}

banned, err := c.collectActiveBannedIPs(path)
tmpBanned, err := c.collectActiveBannedIPs(path)
if err != nil {
log.Errorf("can't collect total banned IPs for ip%s from nft: %s", c.version, err)
log.Errorf("can't collect total banned IPs for %s from nft: %s", c.version, err)
} else {
*banned += tmpBanned
}

return droppedPackets, droppedBytes, banned
}

func (n *nft) CollectMetrics() {
Expand All @@ -142,8 +133,16 @@ func (n *nft) CollectMetrics() {
t := time.NewTicker(metrics.MetricCollectionInterval)

for range t.C {
ip4DroppedPackets, ip4DroppedBytes, bannedIP4 := n.v4.collectDropped(path, n.Hooks)
ip6DroppedPackets, ip6DroppedBytes, bannedIP6 := n.v6.collectDropped(path, n.Hooks)
var ip4DroppedPackets, ip4DroppedBytes, bannedIP4,
ip6DroppedPackets, ip6DroppedBytes, bannedIP6 int

for _, context := range n.contexts {
if context.version == "ip" {
context.collectDropped(path, &ip4DroppedPackets, &ip4DroppedBytes, &bannedIP4)
} else if context.version == "ip6" {
context.collectDropped(path, &ip6DroppedPackets, &ip6DroppedBytes, &bannedIP6)
}
}

metrics.TotalDroppedPackets.Set(float64(ip4DroppedPackets + ip6DroppedPackets))
metrics.TotalDroppedBytes.Set(float64(ip6DroppedBytes + ip4DroppedBytes))
Expand Down
113 changes: 46 additions & 67 deletions pkg/nftables/nftables.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,25 @@ const (
)

type nft struct {
v4 *nftContext
v6 *nftContext
contexts []*nftContext
decisionsToAdd []*models.Decision
decisionsToDelete []*models.Decision
DenyAction string
DenyLog bool
DenyLogPrefix string
Hooks []string
}

func NewNFTables(config *cfg.BouncerConfig) (*nft, error) {
contexts := make([]*nftContext, len(config.Nftables.Targets))
for i, target := range config.Nftables.Targets {
contexts[i] = NewNFTContext(&target)
}

ret := &nft{
v4: NewNFTV4Context(config),
v6: NewNFTV6Context(config),
contexts: contexts,
DenyAction: config.DenyAction,
DenyLog: config.DenyLog,
DenyLogPrefix: config.DenyLogPrefix,
Hooks: config.NftablesHooks,
}

return ret, nil
Expand All @@ -49,12 +50,10 @@ func NewNFTables(config *cfg.BouncerConfig) (*nft, error) {
func (n *nft) Init() error {
log.Debug("nftables: Init()")

if err := n.v4.init(n.Hooks, n.DenyLog, n.DenyLogPrefix, n.DenyAction); err != nil {
return err
}

if err := n.v6.init(n.Hooks, n.DenyLog, n.DenyLogPrefix, n.DenyAction); err != nil {
return err
for _, context := range n.contexts {
if err := context.init(n.DenyLog, n.DenyLogPrefix, n.DenyAction); err != nil {
return err
}
}

log.Infof("nftables initiated")
Expand All @@ -69,12 +68,10 @@ func (n *nft) Add(decision *models.Decision) error {

func (n *nft) getBannedState() (map[string]struct{}, error) {
banned := make(map[string]struct{})
if err := n.v4.setBanned(banned); err != nil {
return nil, err
}

if err := n.v6.setBanned(banned); err != nil {
return nil, err
for _, context := range n.contexts {
if err := context.setBanned(banned); err != nil {
return nil, err
}
}

return banned, nil
Expand Down Expand Up @@ -103,36 +100,25 @@ func (n *nft) commitDeletedDecisions() error {
continue
}

log.Tracef("adding %s to buffer", ip)
if strings.Contains(ip.String(), ":") {
if n.v6.conn != nil {
log.Tracef("adding %s to buffer", ip)

ip6 = append(ip6, nftables.SetElement{Key: ip.To16()})
}

continue
}

if n.v4.conn != nil {
log.Tracef("adding %s to buffer", ip)

ip6 = append(ip6, nftables.SetElement{Key: ip.To16()})
} else {
ip4 = append(ip4, nftables.SetElement{Key: ip.To4()})
}
}

if len(ip4) > 0 {
log.Debugf("removing %d ip%s elements from set", len(ip4), n.v4.version)

if err := n.v4.deleteElements(ip4); err != nil {
return err
}
}

if len(ip6) > 0 {
log.Debugf("removing %d ip%s elements from set", len(ip6), n.v6.version)

if err := n.v6.deleteElements(ip6); err != nil {
return err
for _, context := range n.contexts {
if context.version == "ip" && len(ip4) > 0 {
log.Debugf("removing %d %s elements from set", len(ip4), "ip")
if err := context.deleteElements(ip4); err != nil {
return err
}
} else if context.version == "ip6" && len(ip6) > 0 {
log.Debugf("removing %d %s elements from set", len(ip6), "ip6")
if err := context.deleteElements(ip6); err != nil {
return err
}
}
}

Expand All @@ -159,29 +145,24 @@ func (n *nft) commitAddedDecisions() error {

t, _ := time.ParseDuration(*decision.Duration)

log.Tracef("adding %s to buffer", ip)
if strings.Contains(ip.String(), ":") {
if n.v6.conn != nil {
log.Tracef("adding %s to buffer", ip)

ip6 = append(ip6, nftables.SetElement{Timeout: t, Key: ip.To16()})
}

continue
}

if n.v4.conn != nil {
log.Tracef("adding %s to buffer", ip)

ip6 = append(ip6, nftables.SetElement{Timeout: t, Key: ip.To16()})
} else {
ip4 = append(ip4, nftables.SetElement{Timeout: t, Key: ip.To4()})
}
}

if err := n.v4.addElements(ip4); err != nil {
return err
}

if err := n.v6.addElements(ip6); err != nil {
return err
for _, context := range n.contexts {
if context.version == "ip" && len(ip4) > 0 {
if err := context.addElements(ip4); err != nil {
return err
}
} else if context.version == "ip6" && len(ip6) > 0 {
if err := context.addElements(ip6); err != nil {
return err
}
}
}

return nil
Expand Down Expand Up @@ -235,12 +216,10 @@ func (n *nft) Delete(decision *models.Decision) error {
}

func (n *nft) ShutDown() error {
if err := n.v4.shutDown(); err != nil {
return err
}

if err := n.v6.shutDown(); err != nil {
return err
for _, context := range n.contexts {
if err := context.shutDown(); err != nil {
return err
}
}

return nil
Expand Down
Loading