File: custom.go

package info (click to toggle)
crowdsec-custom-bouncer 0.0.15-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 652 kB
  • sloc: sh: 171; makefile: 65
file content (134 lines) | stat: -rw-r--r-- 3,738 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"os/exec"
	"strconv"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/crowdsecurity/crowdsec/pkg/models"
)

type DecisionKey struct {
	Value string
	Type  string
}

type DecisionWithAction struct {
	models.Decision
	Action string `json:"action,omitempty"`
}

type customBouncer struct {
	path                    string
	binaryStdin             io.Writer
	feedViaStdin            bool
	newDecisionValueSet     map[DecisionKey]struct{}
	expiredDecisionValueSet map[DecisionKey]struct{}
}

func newCustomBouncer(cfg *bouncerConfig) (*customBouncer, error) {
	return &customBouncer{
		path:         cfg.BinPath,
		feedViaStdin: cfg.FeedViaStdin,
	}, nil
}

func (c *customBouncer) ResetCache() {
	cachedDecisionCount := len(c.newDecisionValueSet) + len(c.expiredDecisionValueSet)
	if cachedDecisionCount != 0 {
		log.Debugf("resetting cache, clearing %d decisions", cachedDecisionCount)
		// dont return here, because this could be used to intiate the sets
	}
	c.newDecisionValueSet = make(map[DecisionKey]struct{})
	c.expiredDecisionValueSet = make(map[DecisionKey]struct{})
}

func (c *customBouncer) Init() error {
	c.ResetCache()
	return nil
}

func (c *customBouncer) Add(decision *models.Decision) error {
	if _, exists := c.newDecisionValueSet[decisionToDecisionKey(decision)]; exists {
		return nil
	}
	banDuration, err := time.ParseDuration(*decision.Duration)
	if err != nil {
		return err
	}
	log.Debugf("custom [%s] : add ban on %s for %s sec (%s)", c.path, *decision.Value, strconv.Itoa(int(banDuration.Seconds())), *decision.Scenario)
	var str string
	if c.feedViaStdin {
		str, err = serializeDecision(decision, "add")
	} else {
		str, err = serializeDecision(decision, "")
	}
	if err != nil {
		log.Warningf("serialize: %s", err)
	}
	if c.feedViaStdin {
		fmt.Fprintln(c.binaryStdin, str)
		return nil
	}
	cmd := exec.Command(c.path, "add", *decision.Value, strconv.Itoa(int(banDuration.Seconds())), *decision.Scenario, str)
	if out, err := cmd.CombinedOutput(); err != nil {
		log.Errorf("Error in 'add' command (%s): %v --> %s", cmd.String(), err, string(out))
	}
	c.newDecisionValueSet[decisionToDecisionKey(decision)] = struct{}{}
	return nil
}

func (c *customBouncer) Delete(decision *models.Decision) error {
	if _, exists := c.expiredDecisionValueSet[decisionToDecisionKey(decision)]; exists {
		return nil
	}
	banDuration, err := time.ParseDuration(*decision.Duration)
	if err != nil {
		return err
	}
	var str string
	if c.feedViaStdin {
		str, err = serializeDecision(decision, "del")
	} else {
		str, err = serializeDecision(decision, "")
	}
	if c.feedViaStdin {
		fmt.Fprintln(c.binaryStdin, str)
		return nil
	}
	if err != nil {
		log.Warningf("serialize: %s", err)
	}
	log.Debugf("custom [%s] : del ban on %s for %s sec (%s)", c.path, *decision.Value, strconv.Itoa(int(banDuration.Seconds())), *decision.Scenario)
	cmd := exec.Command(c.path, "del", *decision.Value, strconv.Itoa(int(banDuration.Seconds())), *decision.Scenario, str)
	if out, err := cmd.CombinedOutput(); err != nil {
		log.Errorf("Error in 'del' command (%s): %v --> %s", cmd.String(), err, string(out))
	}
	c.expiredDecisionValueSet[decisionToDecisionKey(decision)] = struct{}{}
	return nil
}

func (c *customBouncer) ShutDown() error {
	return nil
}

func serializeDecision(decision *models.Decision, action string) (string, error) {
	d := DecisionWithAction{Decision: *decision, Action: action}
	serbyte, err := json.Marshal(d)
	if err != nil {
		return "", fmt.Errorf("serialize error : %s", err)
	}
	return string(serbyte), nil
}

func decisionToDecisionKey(decision *models.Decision) DecisionKey {
	return DecisionKey{
		Value: *decision.Value,
		Type:  *decision.Type,
	}
}