diff --git a/backend.go b/backend.go index 8c86d688..5e4610d5 100644 --- a/backend.go +++ b/backend.go @@ -48,16 +48,17 @@ func (b *backendCTX) Delete(decision *models.Decision) error { return nil } -func newBackend(backendType string, disableIPV6 bool) (*backendCTX, error) { +func newBackend(config *bouncerConfig) (*backendCTX, error) { var ok bool + b := &backendCTX{} - log.Printf("backend type : %s", backendType) - if disableIPV6 { + log.Printf("backend type : %s", config.Mode) + if config.DisableIPV6 { log.Println("IPV6 is disabled") } - switch backendType { + switch config.Mode { case "iptables": - tmpCtx, err := newIPTables(disableIPV6) + tmpCtx, err := newIPTables(config) if err != nil { return nil, err } @@ -66,7 +67,7 @@ func newBackend(backendType string, disableIPV6 bool) (*backendCTX, error) { return nil, fmt.Errorf("unexpected type '%T' for iptables context", tmpCtx) } case "nftables": - tmpCtx, err := newNFTables(disableIPV6) + tmpCtx, err := newNFTables(config) if err != nil { return nil, err } @@ -75,7 +76,7 @@ func newBackend(backendType string, disableIPV6 bool) (*backendCTX, error) { return nil, fmt.Errorf("unexpected type '%T' for nftables context", tmpCtx) } default: - return b, fmt.Errorf("firewall '%s' is not supported", backendType) + return b, fmt.Errorf("firewall '%s' is not supported", config.Mode) } return b, nil } diff --git a/config.go b/config.go index f8fd26be..cbabc053 100644 --- a/config.go +++ b/config.go @@ -22,6 +22,8 @@ type bouncerConfig struct { APIUrl string `yaml:"api_url"` APIKey string `yaml:"api_key"` DisableIPV6 bool `yaml:"disable_ipv6"` + //specific to iptables, following https://github.com/crowdsecurity/cs-firewall-bouncer/issues/19 + IptablesChains []string `yaml:"iptables_chains"` } func NewConfig(configPath string) (*bouncerConfig, error) { diff --git a/config/cs-firewall-bouncer.yaml b/config/cs-firewall-bouncer.yaml index 63bfc006..8b3ec6dc 100644 --- a/config/cs-firewall-bouncer.yaml +++ b/config/cs-firewall-bouncer.yaml @@ -7,4 +7,12 @@ log_dir: /var/log/ log_level: info api_url: http://localhost:8080/ api_key: ${API_KEY} -disable_ipv6: false \ No newline at end of file +disable_ipv6: false +#if present, insert rule in those chains +iptables_chains: + - INPUT +# - FORWARD +# - DOCKER-USER + + + diff --git a/iptables.go b/iptables.go index 18f40ca6..4e41fbc6 100644 --- a/iptables.go +++ b/iptables.go @@ -16,17 +16,24 @@ type iptables struct { var iptablesCtx = &iptables{} -func newIPTables(disableIPV6 bool) (interface{}, error) { +func newIPTables(config *bouncerConfig) (interface{}, error) { var err error ipv4Ctx := &ipTablesContext{ Name: "ipset", version: "v4", SetName: "crowdsec-blacklists", - StartupCmds: []string{"-I", "INPUT", "-m", "set", "--match-set", "crowdsec-blacklists", "src", "-j", "DROP"}, - ShutdownCmds: []string{"-D", "INPUT", "-m", "set", "--match-set", "crowdsec-blacklists", "src", "-j", "DROP"}, - CheckIptableCmds: []string{"-C", "INPUT", "-m", "set", "--match-set", "crowdsec-blacklists", "src", "-j", "DROP"}, + StartupCmds: [][]string{}, + 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"}) } - ipsetBin, err := exec.LookPath("ipset") if err != nil { return nil, fmt.Errorf("unable to find ipset") @@ -42,14 +49,22 @@ func newIPTables(disableIPV6 bool) (interface{}, error) { v4: ipv4Ctx, } - if !disableIPV6 { + if !config.DisableIPV6 { ipv6Ctx := &ipTablesContext{ Name: "ipset", version: "v6", SetName: "crowdsec6-blacklists", - StartupCmds: []string{"-I", "INPUT", "-m", "set", "--match-set", "crowdsec6-blacklists", "src", "-j", "DROP"}, - ShutdownCmds: []string{"-D", "INPUT", "-m", "set", "--match-set", "crowdsec6-blacklists", "src", "-j", "DROP"}, - CheckIptableCmds: []string{"-C", "INPUT", "-m", "set", "--match-set", "crowdsec6-blacklists", "src", "-j", "DROP"}, + StartupCmds: [][]string{}, + ShutdownCmds: [][]string{}, + CheckIptableCmds: [][]string{}, + } + for _, v := range config.IptablesChains { + ipv6Ctx.StartupCmds = append(ipv6Ctx.StartupCmds, + []string{"-I", v, "-m", "set", "--match-set", "crowdsec6-blacklists", "src", "-j", "DROP"}) + ipv6Ctx.ShutdownCmds = append(ipv6Ctx.ShutdownCmds, + []string{"-D", v, "-m", "set", "--match-set", "crowdsec6-blacklists", "src", "-j", "DROP"}) + 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") diff --git a/iptables_context.go b/iptables_context.go index b959fd80..bf719568 100644 --- a/iptables_context.go +++ b/iptables_context.go @@ -6,6 +6,8 @@ import ( "strings" "time" + "github.com/pkg/errors" + "github.com/crowdsecurity/crowdsec/pkg/models" log "github.com/sirupsen/logrus" ) @@ -15,15 +17,16 @@ type ipTablesContext struct { version string ipsetBin string iptablesBin string - SetName string //crowdsec-netfilter - 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 + SetName string //crowdsec-netfilter + 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 } func (ctx *ipTablesContext) CheckAndCreate() error { var err 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 @@ -42,15 +45,32 @@ func (ctx *ipTablesContext) CheckAndCreate() error { time.Sleep(1 * time.Second) // checking if iptables rules exist - cmd = exec.Command(ctx.iptablesBin, ctx.CheckIptableCmds...) - if _, err := cmd.CombinedOutput(); err != nil { // if doesn't exist, create it - cmd = exec.Command(ctx.iptablesBin, ctx.StartupCmds...) - log.Infof("iptables set-up : %s", cmd.String()) - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("Error while insert set in iptables (%s): %v --> %s", cmd.String(), err, string(out)) + checkOk := true + for _, checkCmd := range ctx.CheckIptableCmds { + cmd = exec.Command(ctx.iptablesBin, checkCmd...) + if stdout, err := cmd.CombinedOutput(); err != nil { + checkOk = false + /*rule doesn't exist, avoid alarming error messages*/ + if strings.Contains(string(stdout), "iptables: Bad rule") { + log.Infof("Rule doesn't exist (%s)", cmd.String()) + } else { + log.Warningf("iptables check command (%s) failed : %s", cmd.String(), err) + log.Debugf("output: %s", string(stdout)) + } + } + } + /*if any of the check command error'ed, exec the setup command*/ + if !checkOk { + // if doesn't exist, create it + for _, startCmd := range ctx.StartupCmds { + cmd = exec.Command(ctx.iptablesBin, startCmd...) + log.Infof("iptables set-up : %s", cmd.String()) + if out, err := cmd.CombinedOutput(); err != nil { + log.Warningf("Error inserting set in iptables (%s): %v : %s", cmd.String(), err, string(out)) + return errors.Wrapf(err, "while inserting set in iptables") + } } } - return nil } @@ -73,18 +93,22 @@ func (ctx *ipTablesContext) add(decision *models.Decision) error { func (ctx *ipTablesContext) shutDown() error { /*clean iptables rules*/ - cmd := exec.Command(ctx.iptablesBin, ctx.ShutdownCmds...) - log.Infof("iptables clean-up : %s", cmd.String()) - if out, err := cmd.CombinedOutput(); err != nil { - /*if the set doesn't exist, don't frigthen user with error messages*/ - if strings.Contains(string(out), "Set crowdsec-blacklists doesn't exist.") { - log.Infof("ipset 'crowdsec-blacklists' doesn't exist, skip") - } else if strings.Contains(string(out), "Set crowdsec6-blacklists doesn't exist.") { - log.Infof("ipset 'crowdsec6-blacklists' doesn't exist, skip") - } else { - log.Errorf("error while removing set entry in iptables : %v --> %s", err, string(out)) + var cmd *exec.Cmd + // if doesn't exist, create it + for _, startCmd := range ctx.ShutdownCmds { + cmd = exec.Command(ctx.iptablesBin, startCmd...) + log.Infof("iptables clean-up : %s", cmd.String()) + if out, err := cmd.CombinedOutput(); err != nil { + if strings.Contains(string(out), "Set crowdsec-blacklists doesn't exist.") { + log.Infof("ipset 'crowdsec-blacklists' doesn't exist, skip") + } else if strings.Contains(string(out), "Set crowdsec6-blacklists doesn't exist.") { + log.Infof("ipset 'crowdsec6-blacklists' doesn't exist, skip") + } else { + log.Errorf("error while removing set entry in iptables : %v --> %s", err, string(out)) + } } } + /*clean ipset set*/ cmd = exec.Command(ctx.ipsetBin, "-exist", "destroy", ctx.SetName) log.Infof("ipset clean-up : %s", cmd.String()) diff --git a/main.go b/main.go index 4882ecd1..d5b53a63 100644 --- a/main.go +++ b/main.go @@ -75,7 +75,7 @@ func main() { log.SetLevel(log.DebugLevel) } - backend, err := newBackend(config.Mode, config.DisableIPV6) + backend, err := newBackend(config) if err != nil { log.Fatalf(err.Error()) } diff --git a/nftables.go b/nftables.go index 8a5cb3ba..f74db322 100644 --- a/nftables.go +++ b/nftables.go @@ -22,11 +22,11 @@ type nft struct { table6 *nftables.Table } -func newNFTables(disableIPV6 bool) (interface{}, error) { +func newNFTables(config *bouncerConfig) (interface{}, error) { ret := &nft{} ret.conn = &nftables.Conn{} - if !disableIPV6 { + if !config.DisableIPV6 { ret.conn6 = &nftables.Conn{} } return ret, nil