Skip to content
This repository has been archived by the owner on Apr 18, 2022. It is now read-only.

Commit

Permalink
Add IPv6 support (#5)
Browse files Browse the repository at this point in the history
* add struct and method

* add IPv6 support
  • Loading branch information
tuxtof authored Apr 10, 2022
1 parent ca14fa2 commit 1382b08
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 51 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,17 @@ Generate a bouncer API key following [CrowdSec documentation](https://doc.crowds
2. Copy the API key printed. You **_WON'T_** be able the get it again.
3. Paste this API key as the value for bouncer environment variable `CROWDSEC_BOUNCER_API_KEY`, instead of "MyApiKey"
4. Start bouncer with `docker-compose up bouncer` in the `example` directory
5. Create `drop Filter Rules` in `input` and `forward` Chain with the `crowdsec Source Address List`
5. Create `IP drop Filter Rules` in `input` and `forward` Chain with the `crowdsec Source Address List`
6. Create `IPv6 drop Filter Rules` in `input` and `forward` Chain with the `crowdsec Source Address List` (if IPv6 used)

```shell
/ip/firewall/filter/
add action=drop src-address-list=crowdsec chain=input in-interface=your-wan-interface place-before=0 comment="crowdsec input drop rules"
add action=drop src-address-list=crowdsec chain=forward in-interface=your-wan-interface place-before=0 comment="crowdsec forward drop rules"

/ipv6/firewall/filter/
add action=drop src-address-list=crowdsec chain=input in-interface=your-wan-interface place-before=0 comment="crowdsec input drop rules"
add action=drop src-address-list=crowdsec chain=forward in-interface=your-wan-interface place-before=0 comment="crowdsec forward drop rules"
```

## Configuration
Expand Down
14 changes: 11 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"

"github.com/go-routeros/routeros"
"github.com/rs/zerolog/log"
"gopkg.in/tomb.v2"

Expand All @@ -11,6 +12,11 @@ import (

var t tomb.Tomb

type mikrotikAddrList struct {
c *routeros.Client
cache map[string]string
}

func main() {

// zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
Expand All @@ -25,8 +31,10 @@ func main() {
log.Fatal().Err(err).Msg("Bouncer init failed")
}

c := initMikrotik()
defer c.Close()
var mal mikrotikAddrList

mal.initMikrotik()
defer mal.c.Close()

t.Go(func() error {
bouncer.Run()
Expand All @@ -41,7 +49,7 @@ func main() {
log.Error().Msg("terminating bouncer process")
return nil
case decisions := <-bouncer.Stream:
decisionProcess(decisions, c)
mal.decisionProcess(decisions)
}
}
})
Expand Down
138 changes: 91 additions & 47 deletions mikrotik.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@ import (
"github.com/go-routeros/routeros"
)

var addrList = make(map[string]string)

func dial() (*routeros.Client, error) {
if useTLS {
return routeros.DialTLS(mikrotikHost, username, password, nil)
}
return routeros.Dial(mikrotikHost, username, password)
}

func initMikrotik() *routeros.Client {
func (mal *mikrotikAddrList) initMikrotik() {

log.Info().Msg("Connecting to mikrotik")

Expand All @@ -32,65 +30,111 @@ func initMikrotik() *routeros.Client {
c.Async()
}

log.Print("mikrotik list addr")
initCmd := "/ip/firewall/address-list/print ?list=crowdsec =.proplist=.id,address"
r, err := c.RunArgs(strings.Split(initCmd, " "))
if err != nil {
log.Fatal().Err(err).Msg("address-list print failed")
}
log.Info().Msgf("fill %d entry in internal addrList\n", len(r.Re))
for _, v := range r.Re {
addrList[v.Map["address"]] = v.Map[".id"]
}
mal.c = c

mal.cache = make(map[string]string)

return c
protos := []string{"ip", "ipv6"}

for _, proto := range protos {
log.Info().Msgf("mikrotik %s list addr", proto)
initCmd := fmt.Sprintf("/%s/firewall/address-list/print ?list=crowdsec =.proplist=.id,address", proto)
r, err := c.RunArgs(strings.Split(initCmd, " "))
if err != nil {
log.Fatal().Err(err).Msg("address-list print failed")
}
log.Info().Msgf("fill %d entry in internal addrList\n", len(r.Re))
for _, v := range r.Re {
mal.cache[v.Map["address"]] = v.Map[".id"]
}
}
}

func decisionProcess(streamDecision *models.DecisionsStreamResponse, c *routeros.Client) {
func (mal *mikrotikAddrList) add(decision *models.Decision) {

for _, decision := range streamDecision.Deleted {
log.Info().Msgf("removed decisions: IP: %s | Scenario: %s | Duration: %s | Scope : %v", *decision.Value, *decision.Scenario, *decision.Duration, *decision.Scope)
log.Info().Msgf("new decisions from %s: IP: %s | Scenario: %s | Duration: %s | Scope : %v", *decision.Origin, *decision.Value, *decision.Scenario, *decision.Duration, *decision.Scope)

if addrList[*decision.Value] != "" {
log.Info().Msgf("Verify address %s in mikrotik", *decision.Value)
checkCmd := fmt.Sprintf("/ip/firewall/address-list/print =.proplist=address ?.id=%s", addrList[*decision.Value])
r, err := c.RunArgs(strings.Split(checkCmd, " "))
if err != nil {
log.Fatal().Err(err).Msg("address-list search cmd failed")
}
var proto string
if strings.Contains(*decision.Value, ":") {
proto = "ipv6"
} else {
proto = "ip"
}

if len(r.Re) == 1 && r.Re[0].Map["address"] == *decision.Value {
delCmd := fmt.Sprintf("/ip/firewall/address-list/remove =numbers=%s", addrList[*decision.Value])
_, err = c.RunArgs(strings.Split(delCmd, " "))
if err != nil {
log.Error().Err(err).Msg("address-list remove cmd failed")
}
log.Info().Msgf("%s removed from mikrotik", *decision.Value)
} else {
log.Info().Msgf("%s already removed from mikrotik", *decision.Value)
}
delete(addrList, *decision.Value)
var address string
if *decision.Scope == "Ip" && proto == "ipv6" {
address = fmt.Sprintf("%s/128", *decision.Value)
} else {
address = *decision.Value
}

addCmd := fmt.Sprintf("/%s/firewall/address-list/add#=list=crowdsec#=address=%s#=comment=%s#=timeout=%s", proto, address, *decision.Scenario, *decision.Duration)

if mal.cache[address] != "" {
log.Info().Msgf("Address %s already present", address)
} else {

r, err := mal.c.RunArgs(strings.Split(addCmd, "#"))
log.Info().Msgf("resp %s", r)
if err != nil {
log.Error().Err(err).Msgf("%s address-list add cmd failed", proto)
} else {
log.Info().Msgf("%s not find in local cache", *decision.Value)
mal.cache[address] = r.Done.List[0].Value
log.Info().Msgf("Address %s blocked in mikrotik", address)
}
}
}

func (mal *mikrotikAddrList) remove(decision *models.Decision) {

log.Info().Msgf("removed decisions: IP: %s | Scenario: %s | Duration: %s | Scope : %v", *decision.Value, *decision.Scenario, *decision.Duration, *decision.Scope)

var proto string
if strings.Contains(*decision.Value, ":") {
proto = "ipv6"
} else {
proto = "ip"
}
for _, decision := range streamDecision.New {
log.Info().Msgf("new decisions from %s: IP: %s | Scenario: %s | Duration: %s | Scope : %v", *decision.Origin, *decision.Value, *decision.Scenario, *decision.Duration, *decision.Scope)

addCmd := fmt.Sprintf("/ip/firewall/address-list/add#=list=crowdsec#=address=%s#=comment=%s#=timeout=%s", *decision.Value, *decision.Scenario, *decision.Duration)
var address string
if *decision.Scope == "Ip" && proto == "ipv6" {
address = fmt.Sprintf("%s/128", *decision.Value)
} else {
address = *decision.Value
}

if addrList[*decision.Value] != "" {
log.Info().Msgf("Address %s already present", *decision.Value)
} else {
r, err := c.RunArgs(strings.Split(addCmd, "#"))
if mal.cache[address] != "" {

log.Info().Msgf("Verify address %s in mikrotik", address)
checkCmd := fmt.Sprintf("/%s/firewall/address-list/print =.proplist=address ?.id=%s", proto, mal.cache[address])
r, err := mal.c.RunArgs(strings.Split(checkCmd, " "))
if err != nil {
log.Fatal().Err(err).Msgf("%s address-list search cmd failed", proto)
}

if len(r.Re) == 1 && r.Re[0].Map["address"] == address {
delCmd := fmt.Sprintf("/%s/firewall/address-list/remove =numbers=%s", proto, mal.cache[address])
_, err = mal.c.RunArgs(strings.Split(delCmd, " "))
if err != nil {
log.Error().Err(err).Msg("address-list add cmd failed")
} else {
addrList[*decision.Value] = r.Done.List[0].Value
log.Info().Msgf("Address %s blocked in mikrotik", *decision.Value)
log.Error().Err(err).Msgf("%s address-list remove cmd failed", proto)
}
log.Info().Msgf("%s removed from mikrotik", address)
} else {
log.Info().Msgf("%s already removed from mikrotik", address)
}
delete(mal.cache, address)

} else {
log.Info().Msgf("%s not find in local cache", address)
}
}

func (mal *mikrotikAddrList) decisionProcess(streamDecision *models.DecisionsStreamResponse) {

for _, decision := range streamDecision.Deleted {
mal.remove(decision)
}
for _, decision := range streamDecision.New {
mal.add(decision)
}
}

0 comments on commit 1382b08

Please sign in to comment.