Skip to content

Commit

Permalink
Merge pull request #26 from crowdsecurity/no_set_deletion
Browse files Browse the repository at this point in the history
support "ipset" mode
  • Loading branch information
buixor authored Feb 2, 2021
2 parents f758a3d + 46bb32e commit 4cea3c2
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 108 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ BINARY_NAME=cs-firewall-bouncer

RELDIR = "cs-firewall-bouncer-${BUILD_VERSION}"

all: clean test build

goversion:
CURRENT_GOVERSION="$(shell go version | cut -d " " -f3 | sed -r 's/[go]+//g')"
REQUIRE_GOVERSION="1.13"
RESPECT_VERSION="$(shell echo "$(CURRENT_GOVERSION),$(REQUIRE_GOVERSION)" | tr ',' '\n' | sort -V)"


all: clean test build

static: clean
$(GOBUILD) $(LD_OPTS) -o $(BINARY_NAME) -v -a -tags netgo -ldflags '-w -extldflags "-static"'
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ cs-firewall-bouncer will fetch new and old decisions from a CrowdSec API to add
Supported firewalls:
- iptables (IPv4 :heavy_check_mark: / IPv6 :heavy_check_mark: )
- nftables (IPv4 :heavy_check_mark: / IPv6 :heavy_check_mark: )
- ipset only (IPv4 :heavy_check_mark: / IPv6 :heavy_check_mark: )

## Installation

Expand Down Expand Up @@ -58,7 +59,7 @@ sudo ./upgrade.sh

## Configuration

To be functional, the `cs-firewall-bouncer` service must be able to comunicate with the local API.
To be functional, the `cs-firewall-bouncer` service must be able to authenticate with the local API.
The `install.sh` script will take care of it (it will call `cscli bouncers add` on your behalf).
If it was not the case, the default configuration file is located under : `/etc/crowdsec/cs-firewall-bouncer/`

Expand All @@ -82,22 +83,22 @@ iptables_chains:
- FORWARD
```
- `mode` can be set to `iptables` or `nftables`
- `mode` can be set to `iptables`, `nftables` or `ipset`
- `update_frequency` controls how often the bouncer is going to query the local API
- `api_url` and `api_key` control local API parameters.
- `iptables_chains` allows (in _iptables_ mode) to control in which chain rules are going to be inserted. (if empty,the bouncer will only maintain ipset lists)
- `iptables_chains` allows (in _iptables_ mode) to control in which chain rules are going to be inserted. (if empty, bouncer will only maintain ipset lists)

You can then start the service:

```sh
sudo systemctl start cs-firewall-bouncer
```

### iptables vs nftables
### modes

The bouncer supports two modes : `iptables` or `nftables`.
When using `nftables`, it doesn't directly rely on any available command, but rather on github.com/google/nftables.
When using `iptables`, it relies on `iptables` and `ipset` commands.
- mode `nftables` relies on github.com/google/nftables to create table, chain and set.
- mode `iptables` relies on `iptables` and `ipset` commands to insert `match-set` directives and maintain associated ipsets
- mode `ipset` relies on `ipset` and only manage contents of the sets (they need to exist at startup and will be flushed rather than created)



Expand Down
2 changes: 1 addition & 1 deletion backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func newBackend(config *bouncerConfig) (*backendCTX, error) {
log.Println("IPV6 is disabled")
}
switch config.Mode {
case "iptables":
case "iptables", "ipset":
tmpCtx, err := newIPTables(config)
if err != nil {
return nil, err
Expand Down
125 changes: 37 additions & 88 deletions iptables.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var iptablesCtx = &iptables{}

func newIPTables(config *bouncerConfig) (interface{}, error) {
var err error
var ret *iptables = &iptables{}
ipv4Ctx := &ipTablesContext{
Name: "ipset",
version: "v4",
Expand All @@ -26,37 +27,47 @@ func newIPTables(config *bouncerConfig) (interface{}, error) {
ShutdownCmds: [][]string{},
CheckIptableCmds: [][]string{},
}
for _, v := range config.IptablesChains {
ipv4Ctx.StartupCmds = append(ipv4Ctx.StartupCmds,
[]string{"-I", v, "-m", "set", "--match-set", "crowdsec-blacklists", "src", "-j", "DROP"})
ipv4Ctx.ShutdownCmds = append(ipv4Ctx.ShutdownCmds,
[]string{"-D", v, "-m", "set", "--match-set", "crowdsec-blacklists", "src", "-j", "DROP"})
ipv4Ctx.CheckIptableCmds = append(ipv4Ctx.CheckIptableCmds,
[]string{"-C", v, "-m", "set", "--match-set", "crowdsec-blacklists", "src", "-j", "DROP"})
ipv6Ctx := &ipTablesContext{
Name: "ipset",
version: "v6",
SetName: "crowdsec6-blacklists",
StartupCmds: [][]string{},
ShutdownCmds: [][]string{},
CheckIptableCmds: [][]string{},
}

ipsetBin, err := exec.LookPath("ipset")
if err != nil {
return nil, fmt.Errorf("unable to find ipset")
}

ipv4Ctx.iptablesBin, err = exec.LookPath("iptables")
if err != nil {
return nil, fmt.Errorf("unable to find iptables")
}
ipv4Ctx.ipsetBin = ipsetBin

ret := &iptables{
v4: ipv4Ctx,
}

if !config.DisableIPV6 {
ipv6Ctx := &ipTablesContext{
Name: "ipset",
version: "v6",
SetName: "crowdsec6-blacklists",
StartupCmds: [][]string{},
ShutdownCmds: [][]string{},
CheckIptableCmds: [][]string{},
if config.Mode == "ipset" {
ipv4Ctx.ipsetContentOnly = true
} else {
ipv4Ctx.iptablesBin, err = exec.LookPath("iptables")
if err != nil {
return nil, fmt.Errorf("unable to find iptables")
}
for _, v := range config.IptablesChains {
ipv4Ctx.StartupCmds = append(ipv4Ctx.StartupCmds,
[]string{"-I", v, "-m", "set", "--match-set", "crowdsec-blacklists", "src", "-j", "DROP"})
ipv4Ctx.ShutdownCmds = append(ipv4Ctx.ShutdownCmds,
[]string{"-D", v, "-m", "set", "--match-set", "crowdsec-blacklists", "src", "-j", "DROP"})
ipv4Ctx.CheckIptableCmds = append(ipv4Ctx.CheckIptableCmds,
[]string{"-C", v, "-m", "set", "--match-set", "crowdsec-blacklists", "src", "-j", "DROP"})
}
}
ret.v4 = ipv4Ctx
if config.DisableIPV6 {
return ret, nil
}
ipv6Ctx.ipsetBin = ipsetBin
if config.Mode == "ipset" {
ipv6Ctx.ipsetContentOnly = true
} else {
ipv6Ctx.iptablesBin, err = exec.LookPath("ip6tables")
if err != nil {
return nil, fmt.Errorf("unable to find ip6tables")
}
for _, v := range config.IptablesChains {
ipv6Ctx.StartupCmds = append(ipv6Ctx.StartupCmds,
Expand All @@ -66,13 +77,8 @@ func newIPTables(config *bouncerConfig) (interface{}, error) {
ipv6Ctx.CheckIptableCmds = append(ipv6Ctx.CheckIptableCmds,
[]string{"-C", v, "-m", "set", "--match-set", "crowdsec6-blacklists", "src", "-j", "DROP"})
}
ipv6Ctx.ipsetBin = ipsetBin
ipv6Ctx.iptablesBin, err = exec.LookPath("ip6tables")
if err != nil {
return nil, fmt.Errorf("unable to find iptables")
}
ret.v6 = ipv6Ctx
}
ret.v6 = ipv6Ctx

return ret, nil
}
Expand Down Expand Up @@ -180,60 +186,3 @@ func (ipt *iptables) Delete(decision *models.Decision) error {
}
return nil
}

/*func (ipt *iptables) Run(dbCTX *database.Context, frequency time.Duration) error {
lastDelTS := time.Now()
lastAddTS := time.Now()
//start by getting valid bans in db ^^
log.Infof("fetching existing bans from DB")
bansToAdd, err := dbCTX.GetNewBan()
if err != nil {
return err
}
log.Infof("found %d bans in DB", len(bansToAdd))
for idx, ba := range bansToAdd {
log.Debugf("ban %d/%d", idx, len(bansToAdd))
if err := ipt.AddBan(ba); err != nil {
return err
}
}
for {
// check if ipset set and iptables rules are still present. if not creat them
if err := ipt.v4.CheckAndCreate(); err != nil {
return err
}
if err := ipt.v6.CheckAndCreate(); err != nil {
return err
}
time.Sleep(frequency)
bas, err := dbCTX.GetDeletedBanSince(lastDelTS)
if err != nil {
return err
}
lastDelTS = time.Now()
if len(bas) > 0 {
log.Infof("%d bans to flush since %s", len(bas), lastDelTS)
}
for idx, ba := range bas {
log.Debugf("delete ban %d/%d", idx, len(bas))
if err := ipt.DeleteBan(ba); err != nil {
return err
}
}
bansToAdd, err := dbCTX.GetNewBanSince(lastAddTS)
if err != nil {
return err
}
lastAddTS = time.Now()
for idx, ba := range bansToAdd {
log.Debugf("ban %d/%d", idx, len(bansToAdd))
if err := ipt.AddBan(ba); err != nil {
return err
}
}
}
}
*/
34 changes: 23 additions & 11 deletions iptables_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type ipTablesContext struct {
StartupCmds [][]string //-I INPUT -m set --match-set myset src -j DROP
ShutdownCmds [][]string //-D INPUT -m set --match-set myset src -j DROP
CheckIptableCmds [][]string
ipsetContentOnly bool
}

func (ctx *ipTablesContext) CheckAndCreate() error {
Expand All @@ -29,15 +30,21 @@ func (ctx *ipTablesContext) CheckAndCreate() error {
log.Infof("Checking existing set")
/* check if the set already exist */
cmd := exec.Command(ctx.ipsetBin, "-L", ctx.SetName)
if _, err = cmd.CombinedOutput(); err != nil { // if doesn't exist, create it
if ctx.version == "v6" {
cmd = exec.Command(ctx.ipsetBin, "-exist", "create", ctx.SetName, "nethash", "timeout", "300", "family", "inet6")
if _, err = cmd.CombinedOutput(); err != nil { //it doesn't exist
if ctx.ipsetContentOnly {
/*if we manage ipset content only, error*/
log.Errorf("set %s doesn't exist, can't manage content", ctx.SetName)
return errors.Wrapf(err, "set %s doesn't exist", ctx.SetName)
} else {
cmd = exec.Command(ctx.ipsetBin, "-exist", "create", ctx.SetName, "nethash", "timeout", "300")
}
log.Infof("ipset set-up : %s", cmd.String())
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("Error while creating set : %v --> %s", err, string(out))
if ctx.version == "v6" {
cmd = exec.Command(ctx.ipsetBin, "-exist", "create", ctx.SetName, "nethash", "timeout", "300", "family", "inet6")
} else {
cmd = exec.Command(ctx.ipsetBin, "-exist", "create", ctx.SetName, "nethash", "timeout", "300")
}
log.Infof("ipset set-up : %s", cmd.String())
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("Error while creating set : %v --> %s", err, string(out))
}
}
}

Expand Down Expand Up @@ -110,16 +117,21 @@ func (ctx *ipTablesContext) shutDown() error {
}

/*clean ipset set*/
cmd = exec.Command(ctx.ipsetBin, "-exist", "destroy", ctx.SetName)
var ipsetCmd string
if ctx.ipsetContentOnly {
ipsetCmd = "flush"
} else {
ipsetCmd = "destroy"
}
cmd = exec.Command(ctx.ipsetBin, "-exist", ipsetCmd, ctx.SetName)
log.Infof("ipset clean-up : %s", cmd.String())
if out, err := cmd.CombinedOutput(); err != nil {
if strings.Contains(string(out), "The set with the given name does not exist") {
log.Infof("ipset 'crowdsec-blacklists' doesn't exist, skip")
} else {
log.Errorf("Error while destroying set : %v --> %s", err, string(out))
log.Errorf("set %s error : %v - %s", ipsetCmd, err, string(out))
}
}

return nil
}

Expand Down

0 comments on commit 4cea3c2

Please sign in to comment.