Skip to content

Commit

Permalink
Merge pull request #4 from sbs2001/main
Browse files Browse the repository at this point in the history
Make lapi and cloudfare API update frequency configurable individually
  • Loading branch information
sbs2001 authored May 10, 2021
2 parents 4473cc1 + c1176f8 commit bd63426
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 41 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,16 @@ Configuration file can be found at `/etc/crowdsec/cs-cloudfare-bouncer/cs-cloudf
# CrowdSec Config
crowdsec_lapi_url: http://localhost:8080/
crowdsec_lapi_key: ${LAPI_KEY}
crowdsec_update_frequency: 10s

# Cloudfare Config
cloudfare_api_token: ${CF_TOKEN}
cloudfare_account_id: ${CF_ACC_ID}
cloudfare_zone_id: ${CF_ZONE_ID}

cloudflare_update_frequency: 30s
cloudfare_ip_list_name: crowdsec

# Bouncer Config
update_frequency: 10s
action: block
daemon: true
log_mode: file
Expand Down
36 changes: 21 additions & 15 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@ import (
)

type bouncerConfig struct {
CrowdSecLAPIUrl string `yaml:"crowdsec_lapi_url"`
CrowdSecLAPIKey string `yaml:"crowdsec_lapi_key"`
CloudflareAPIToken string `yaml:"cloudfare_api_token"`
CloudflareAccountID string `yaml:"cloudfare_account_id"`
CloudflareZoneID string `yaml:"cloudfare_zone_id"`
CloudflareIPListName string `yaml:"cloudfare_ip_list_name"`
updateFrequency time.Duration
Action string `yaml:"action"`
Daemon bool `yaml:"daemon"`
UpdateFrequencyYAML string `yaml:"update_frequency"`
LogMode string `yaml:"log_mode"`
LogDir string `yaml:"log_dir"`
LogLevel log.Level `yaml:"log_level"`
CrowdSecLAPIUrl string `yaml:"crowdsec_lapi_url"`
CrowdSecLAPIKey string `yaml:"crowdsec_lapi_key"`
CrowdsecUpdateFrequencyYAML string `yaml:"crowdsec_update_frequency"`
CloudflareAPIToken string `yaml:"cloudfare_api_token"`
CloudflareAccountID string `yaml:"cloudfare_account_id"`
CloudflareZoneID string `yaml:"cloudfare_zone_id"`
CloudflareIPListName string `yaml:"cloudfare_ip_list_name"`
CloudflareUpdateFrequencyYAML string `yaml:"cloudflare_update_frequency"`
CloudflareUpdateFrequency time.Duration `yaml:"-"`
Action string `yaml:"action"`
Daemon bool `yaml:"daemon"`
LogMode string `yaml:"log_mode"`
LogDir string `yaml:"log_dir"`
LogLevel log.Level `yaml:"log_level"`
}

// NewConfig creates bouncerConfig from the file at provided path
Expand All @@ -42,9 +43,14 @@ func NewConfig(configPath string) (*bouncerConfig, error) {
return nil, fmt.Errorf("failed to unmarshal %s : %v", configPath, err)
}

config.updateFrequency, err = time.ParseDuration(config.UpdateFrequencyYAML)
config.CloudflareUpdateFrequency, err = time.ParseDuration(config.CrowdsecUpdateFrequencyYAML)
if err != nil {
return nil, fmt.Errorf("invalid update frequency %s : %s", config.UpdateFrequencyYAML, err)
return nil, fmt.Errorf("invalid update frequency %s : %s", config.CrowdsecUpdateFrequencyYAML, err)
}

_, err = time.ParseDuration(config.CloudflareUpdateFrequencyYAML)
if err != nil {
return nil, fmt.Errorf("invalid update frequency %s : %s", config.CloudflareUpdateFrequencyYAML, err)
}

if config.Action == "" {
Expand Down
4 changes: 2 additions & 2 deletions config/cs-cloudfare-bouncer.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# CrowdSec Config
crowdsec_lapi_url: http://localhost:8080/
crowdsec_lapi_key: ${LAPI_KEY}
crowdsec_update_frequency: 10s

# Cloudfare Config
cloudfare_api_token: ${CF_TOKEN}
cloudfare_account_id: ${CF_ACC_ID}
cloudfare_zone_id: ${CF_ZONE_ID}

cloudflare_update_frequency: 30s
cloudfare_ip_list_name: crowdsec

# Bouncer Config
update_frequency: 10s
action: block
daemon: true
log_mode: file
Expand Down
55 changes: 33 additions & 22 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/signal"
"strings"
"syscall"
"time"

"github.com/cloudflare/cloudflare-go"
"github.com/coreos/go-systemd/daemon"
Expand Down Expand Up @@ -133,48 +134,38 @@ func main() {
csLapi := &csbouncer.StreamBouncer{
APIKey: conf.CrowdSecLAPIKey,
APIUrl: conf.CrowdSecLAPIUrl,
TickerInterval: conf.UpdateFrequencyYAML,
TickerInterval: conf.CrowdsecUpdateFrequencyYAML,
}

if err := csLapi.Init(); err != nil {
log.Fatalf(err.Error())
}

cloudflareTicker := time.NewTicker(conf.CloudflareUpdateFrequency)

go csLapi.Run()

cloudflareIDByIP := make(map[string]string)
// These maps are used to create slices without dup IPS
deleteIPMap := make(map[cloudflare.IPListItemDeleteItemRequest]bool)
addIPMap := make(map[cloudflare.IPListItemCreateRequest]bool)

t.Go(func() error {
log.Printf("processing new and deleted decisions . . .")
for {
select {
case <-t.Dying():
return errors.New("tomb dying")

case streamDecision := <-csLapi.Stream:
deleteIPMap := make(map[cloudflare.IPListItemDeleteItemRequest]bool)
addIPMap := make(map[cloudflare.IPListItemCreateRequest]bool)
for _, decision := range streamDecision.Deleted {
if _, ok := cloudflareIDByIP[*decision.Value]; ok {
deleteIPMap[cloudflare.IPListItemDeleteItemRequest{ID: cloudflareIDByIP[*decision.Value]}] = true
delete(cloudflareIDByIP, *decision.Value)
}
}

for _, decision := range streamDecision.New {
addIPMap[cloudflare.IPListItemCreateRequest{
IP: *decision.Value,
Comment: "Added by crowdsec bouncer",
}] = true
}

case <-cloudflareTicker.C:
addIPs := make([]cloudflare.IPListItemCreateRequest, 0)
deleteIPs := make([]cloudflare.IPListItemDeleteItemRequest, 0)
for k := range addIPMap {
addIPs = append(addIPs, k)
}

if len(addIPs) > 0 {
ipItems, err := cfAPI.CreateIPListItems(ctx, ipListID, addIPs)
log.Infof("adding '%d' decisions", len(addIPs))
log.Infof("making API call to cloudflare for adding '%d' decisions", len(addIPs))

if err != nil {
log.Fatal(err)
Expand All @@ -185,18 +176,38 @@ func main() {
}
}

deleteIPs := make([]cloudflare.IPListItemDeleteItemRequest, 0)
for k := range deleteIPMap {
deleteIPs = append(deleteIPs, k)
}

if len(deleteIPs) > 0 {
_, err := cfAPI.DeleteIPListItems(ctx, ipListID, cloudflare.IPListItemDeleteRequest{Items: deleteIPs})
log.Infof("deleting '%d' decisions", len(deleteIPs))
log.Infof("making API call to cloudflare to delete '%d' decisions", len(deleteIPs))
if err != nil {
log.Fatal(err)
}
}

// Flush
deleteIPMap = make(map[cloudflare.IPListItemDeleteItemRequest]bool)
addIPMap = make(map[cloudflare.IPListItemCreateRequest]bool)

case streamDecision := <-csLapi.Stream:
log.Printf("processing new and deleted decisions from crowdsec LAPI")
for _, decision := range streamDecision.Deleted {
if _, ok := cloudflareIDByIP[*decision.Value]; ok {
deleteIPMap[cloudflare.IPListItemDeleteItemRequest{ID: cloudflareIDByIP[*decision.Value]}] = true
delete(cloudflareIDByIP, *decision.Value)
}
}

for _, decision := range streamDecision.New {
addIPMap[cloudflare.IPListItemCreateRequest{
IP: *decision.Value,
Comment: "Added by crowdsec bouncer",
}] = true
}

}
}
})
Expand Down

0 comments on commit bd63426

Please sign in to comment.