Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Usage metrics #365

Merged
merged 72 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
e2c6c4e
wip metrics
mmetc Mar 11, 2024
4c5261b
golang.org/x/exp/slices -> slices; drop dependency
mmetc Mar 11, 2024
8cf6979
go mod tidy
mmetc Mar 12, 2024
f8bc039
update metrics model
blotus Apr 22, 2024
4f86e59
update nftables pkg
blotus Apr 22, 2024
e484e80
use nft pkg to get metrics about rules
blotus Apr 22, 2024
aa7eddd
lint
blotus Apr 22, 2024
261f693
nftables: create one rule/set per decision origin
blotus Apr 22, 2024
92eee0a
fix nftables tests
blotus Apr 22, 2024
74222e9
fix nftables tests
blotus Apr 22, 2024
bcb3848
lint
blotus Apr 22, 2024
a3a67ba
lint
blotus Apr 22, 2024
3cab884
send proper metrics
blotus Apr 23, 2024
39698f8
up
blotus Apr 23, 2024
9f988df
lint
blotus Apr 23, 2024
9545c5b
fix origin name for nftables
blotus Jun 14, 2024
6768736
use ipset restore to add ips
blotus Jun 14, 2024
32b849d
up
blotus Jun 25, 2024
659f50d
up
blotus Jul 9, 2024
059a900
send delta metrics
blotus Jul 11, 2024
d6281bb
merge from main
blotus Jul 15, 2024
f19b9f2
update go-cs-bouncer dep
mmetc Jul 15, 2024
60da3ea
docker func test
mmetc Jul 15, 2024
60d9e54
update lint config
mmetc Jul 15, 2024
7782feb
add processed bytes/metrics for nftables
blotus Jul 16, 2024
36eaae4
fix ipset mode
blotus Jul 16, 2024
390bde9
only call ipset restore if needed
blotus Jul 16, 2024
af98783
linter
blotus Jul 16, 2024
5426827
fix tests
blotus Jul 16, 2024
e90389b
Merge branch 'main' into usage-metrics
mmetc Jul 17, 2024
d16cf51
setup tracking chain for global processed bytes/packets
blotus Jul 17, 2024
0e59280
do it for all configured chains
blotus Jul 17, 2024
809a359
store all iptables rules in our own chain for easier management
blotus Jul 18, 2024
865eded
try to increase timeout for tests
blotus Jul 18, 2024
ad49f4a
Merge branch 'main' into usage-metrics
mmetc Jul 18, 2024
c34a688
go mod tidy
mmetc Jul 18, 2024
05de936
emit metrics for iptables
blotus Jul 18, 2024
0c3195d
remove unused type
blotus Jul 18, 2024
d443a9c
do not setup chain when in ipset mode
blotus Jul 18, 2024
71ad36d
update go-cs-bouncer
blotus Jul 22, 2024
1108ac4
send absolute value for active_decisions
blotus Jul 22, 2024
32e5a1c
only compute metrics when requested
blotus Jul 22, 2024
23ee2b3
up
blotus Jul 24, 2024
28b98f5
make linter happy
blotus Aug 16, 2024
dfd41d4
go mod tidy
blotus Aug 16, 2024
df780b2
update deps
blotus Aug 16, 2024
863bfe8
up
blotus Aug 16, 2024
cb05888
up
blotus Aug 16, 2024
c4528d8
remove toolchain pin from go.mod
mmetc Aug 19, 2024
541e172
use go 1.22.6
mmetc Aug 19, 2024
69833b2
update go-cs-bouncer; mod tidy + diff
mmetc Aug 19, 2024
c76de26
Merge branch 'main' into usage-metrics
mmetc Aug 19, 2024
460aaef
1.22
mmetc Aug 19, 2024
6673ccd
increase timeout
blotus Aug 22, 2024
f99d61a
increase timeout
blotus Aug 22, 2024
8400998
update set name in iptables tests
blotus Aug 22, 2024
66ca0f2
properly destroy leftover sets in iptables mode on startup
blotus Aug 22, 2024
45e6086
update test
blotus Aug 22, 2024
efb481f
debug
blotus Aug 22, 2024
2c61972
up
blotus Aug 22, 2024
2087894
up
blotus Aug 22, 2024
77c12c1
up
blotus Aug 22, 2024
22f7788
up
blotus Aug 22, 2024
f5eb4d5
CI: don't pin minor go version
mmetc Aug 23, 2024
8a72046
drop useless error return
blotus Aug 23, 2024
30842eb
remove useless ipsetcmd.Delete
blotus Aug 23, 2024
d5b4668
allow log action
blotus Aug 23, 2024
6b9019f
fix some comments
blotus Aug 23, 2024
e34ed07
fix some comments
blotus Aug 23, 2024
bcb6a05
up
blotus Aug 23, 2024
7ab20c7
better support for metrics in ipset only mode
blotus Sep 16, 2024
7b3e2ef
lint
blotus Sep 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-binary-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22.5
go-version: '1.22'

- name: Build all platforms
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22.5
go-version: '1.22'

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22.5
go-version: '1.22'

- name: mod tidy
run: |
go mod tidy
git diff

- name: Build
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests_deb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22.5
go-version: '1.22'

- name: Cache virtualenvs
id: cache-pipenv
Expand Down
171 changes: 156 additions & 15 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ import (
"net/http"
"os"
"os/signal"
"slices"
"strings"
"syscall"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
io_prometheus_client "github.com/prometheus/client_model/go"
log "github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
"golang.org/x/sync/errgroup"

csbouncer "github.com/crowdsecurity/go-cs-bouncer"
"github.com/crowdsecurity/go-cs-lib/csdaemon"
"github.com/crowdsecurity/go-cs-lib/csstring"
"github.com/crowdsecurity/go-cs-lib/ptr"
"github.com/crowdsecurity/go-cs-lib/version"

"github.com/crowdsecurity/crowdsec/pkg/models"
Expand All @@ -30,7 +33,11 @@ import (
"github.com/crowdsecurity/cs-firewall-bouncer/pkg/metrics"
)

const name = "crowdsec-firewall-bouncer"
const bouncerType = "crowdsec-firewall-bouncer"

type metricsHandler struct {
backend *backend.BackendCTX
}

func backendCleanup(backend *backend.BackendCTX) {
log.Info("Shutting down backend")
Expand Down Expand Up @@ -136,6 +143,134 @@ func addDecisions(backend *backend.BackendCTX, decisions []*models.Decision, con
}
}

func getLabelValue(labels []*io_prometheus_client.LabelPair, key string) string {

for _, label := range labels {
if label.GetName() == key {
return label.GetValue()
}
}

return ""
}

// metricsUpdater receives a metrics struct with basic data and populates it with the current metrics.
func (m metricsHandler) metricsUpdater(met *models.RemediationComponentsMetrics, updateInterval time.Duration) {
log.Debugf("Updating metrics")

m.backend.CollectMetrics()

//Most of the common fields are set automatically by the metrics provider
//We only need to care about the metrics themselves

promMetrics, err := prometheus.DefaultGatherer.Gather()

if err != nil {
log.Errorf("unable to gather prometheus metrics: %s", err)
return
}

met.Metrics = append(met.Metrics, &models.DetailedMetrics{
Meta: &models.MetricsMeta{
UtcNowTimestamp: ptr.Of(time.Now().Unix()),
WindowSizeSeconds: ptr.Of(int64(updateInterval.Seconds())),
},
Items: make([]*models.MetricsDetailItem, 0),
})

for _, metricFamily := range promMetrics {
for _, metric := range metricFamily.GetMetric() {
switch metricFamily.GetName() {
case metrics.ActiveBannedIPsMetricName:
//We send the absolute value, as it makes no sense to try to sum them crowdsec side
labels := metric.GetLabel()
value := metric.GetGauge().GetValue()
origin := getLabelValue(labels, "origin")
ipType := getLabelValue(labels, "ip_type")
log.Debugf("Sending active decisions for %s %s | current value: %f", origin, ipType, value)
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
Name: ptr.Of("active_decisions"),
Value: ptr.Of(value),
Labels: map[string]string{
"origin": origin,
"ip_type": ipType,
},
Unit: ptr.Of("ip"),
})
case metrics.DroppedBytesMetricName:
labels := metric.GetLabel()
value := metric.GetGauge().GetValue()
origin := getLabelValue(labels, "origin")
ipType := getLabelValue(labels, "ip_type")
key := origin + ipType
log.Debugf("Sending dropped bytes for %s %s %f | current value: %f | previous value: %f\n", origin, ipType, value-metrics.LastDroppedBytesValue[key], value, metrics.LastDroppedBytesValue[key])
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
Name: ptr.Of("dropped"),
Value: ptr.Of(value - metrics.LastDroppedBytesValue[key]),
Labels: map[string]string{
"origin": origin,
"ip_type": ipType,
},
Unit: ptr.Of("byte"),
})
metrics.LastDroppedBytesValue[key] = value
case metrics.DroppedPacketsMetricName:
labels := metric.GetLabel()
value := metric.GetGauge().GetValue()
origin := getLabelValue(labels, "origin")
ipType := getLabelValue(labels, "ip_type")
key := origin + ipType
log.Debugf("Sending dropped packets for %s %s %f | current value: %f | previous value: %f\n", origin, ipType, value-metrics.LastDroppedPacketsValue[key], value, metrics.LastDroppedPacketsValue[key])
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
Name: ptr.Of("dropped"),
Value: ptr.Of(value - metrics.LastDroppedPacketsValue[key]),
Labels: map[string]string{
"origin": origin,
"ip_type": ipType,
},
Unit: ptr.Of("packet"),
})
metrics.LastDroppedPacketsValue[key] = value
case metrics.ProcessedBytesMetricName:
labels := metric.GetLabel()
value := metric.GetGauge().GetValue()
ipType := getLabelValue(labels, "ip_type")
log.Debugf("Sending processed bytes for %s %f | current value: %f | previous value: %f\n", ipType, value-metrics.LastProcessedBytesValue[ipType], value, metrics.LastProcessedBytesValue[ipType])
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
Name: ptr.Of("processed"),
Value: ptr.Of(value - metrics.LastProcessedBytesValue[ipType]),
Labels: map[string]string{
"ip_type": ipType,
},
Unit: ptr.Of("byte"),
})
metrics.LastProcessedBytesValue[ipType] = value
case metrics.ProcessedPacketsMetricName:
labels := metric.GetLabel()
value := metric.GetGauge().GetValue()
ipType := getLabelValue(labels, "ip_type")
log.Debugf("Sending processed packets for %s %f | current value: %f | previous value: %f\n", ipType, value-metrics.LastProcessedPacketsValue[ipType], value, metrics.LastProcessedPacketsValue[ipType])
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
Name: ptr.Of("processed"),
Value: ptr.Of(value - metrics.LastProcessedPacketsValue[ipType]),
Labels: map[string]string{
"ip_type": ipType,
},
Unit: ptr.Of("packet"),
})
metrics.LastProcessedPacketsValue[ipType] = value
}
}
}
}

func (m metricsHandler) computeMetricsHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m.backend.CollectMetrics()
next.ServeHTTP(w, r)
})
}

func Execute() error {
configPath := flag.String("c", "", "path to crowdsec-firewall-bouncer.yaml")
verbose := flag.Bool("v", false, "set verbose mode")
Expand Down Expand Up @@ -176,7 +311,7 @@ func Execute() error {
log.SetLevel(log.DebugLevel)
}

log.Infof("Starting crowdsec-firewall-bouncer %s", version.String())
log.Infof("Starting %s %s", bouncerType, version.String())

backend, err := backend.NewBackend(config)
if err != nil {
Expand All @@ -196,7 +331,7 @@ func Execute() error {
return err
}

bouncer.UserAgent = fmt.Sprintf("%s/%s", name, version.String())
bouncer.UserAgent = fmt.Sprintf("%s/%s", bouncerType, version.String())
if err := bouncer.Init(); err != nil {
return fmt.Errorf("unable to configure bouncer: %w", err)
}
Expand All @@ -217,21 +352,27 @@ func Execute() error {
return errors.New("bouncer stream halted")
})

if config.PrometheusConfig.Enabled {
if config.Mode == cfg.IptablesMode || config.Mode == cfg.NftablesMode || config.Mode == cfg.IpsetMode || config.Mode == cfg.PfMode {
go backend.CollectMetrics()
mHandler := metricsHandler{
backend: backend,
}

if config.Mode == cfg.IpsetMode {
prometheus.MustRegister(metrics.TotalActiveBannedIPs)
} else {
prometheus.MustRegister(metrics.TotalDroppedBytes, metrics.TotalDroppedPackets, metrics.TotalActiveBannedIPs)
}
}
metricsProvider, err := csbouncer.NewMetricsProvider(bouncer.APIClient, bouncerType, mHandler.metricsUpdater, log.StandardLogger())
if err != nil {
return fmt.Errorf("unable to create metrics provider: %w", err)
}

prometheus.MustRegister(csbouncer.TotalLAPICalls, csbouncer.TotalLAPIError)
g.Go(func() error {
return metricsProvider.Run(ctx)
})

if config.Mode == cfg.IptablesMode || config.Mode == cfg.NftablesMode || config.Mode == cfg.IpsetMode || config.Mode == cfg.PfMode {
prometheus.MustRegister(metrics.TotalDroppedBytes, metrics.TotalDroppedPackets, metrics.TotalActiveBannedIPs, metrics.TotalProcessedBytes, metrics.TotalProcessedPackets)
}

prometheus.MustRegister(csbouncer.TotalLAPICalls, csbouncer.TotalLAPIError)
if config.PrometheusConfig.Enabled {
go func() {
http.Handle("/metrics", promhttp.Handler())
http.Handle("/metrics", mHandler.computeMetricsHandler(promhttp.Handler()))

listenOn := net.JoinHostPort(
config.PrometheusConfig.ListenAddress,
Expand Down
72 changes: 37 additions & 35 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,61 +1,63 @@
module github.com/crowdsecurity/cs-firewall-bouncer

go 1.21
go 1.22

require (
github.com/crowdsecurity/crowdsec v1.6.1
github.com/crowdsecurity/go-cs-bouncer v0.0.13
github.com/crowdsecurity/go-cs-lib v0.0.10
github.com/google/nftables v0.1.1-0.20230710063801-8a10f689006b
github.com/prometheus/client_golang v1.17.0
github.com/crowdsecurity/crowdsec v1.6.3-rc3
github.com/crowdsecurity/go-cs-bouncer v0.0.14-0.20240819095913-4521d8ddc0c6
github.com/crowdsecurity/go-cs-lib v0.0.13
github.com/google/nftables v0.2.0
github.com/prometheus/client_golang v1.20.0
github.com/prometheus/client_model v0.6.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/sync v0.6.0
golang.org/x/sys v0.19.0
github.com/stretchr/testify v1.9.0
golang.org/x/sync v0.8.0
golang.org/x/sys v0.24.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/antonmedv/expr v1.15.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/blackfireio/osinfo v1.0.5 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.4 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/validate v0.22.1 // indirect
github.com/goccy/go-yaml v1.11.2 // indirect
github.com/expr-lang/expr v1.16.9 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/strfmt v0.23.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
github.com/goccy/go-yaml v1.12.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mdlayher/netlink v1.7.1 // indirect
github.com/mdlayher/socket v0.4.0 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/protobuf v1.33.0 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
go.mongodb.org/mongo-driver v1.16.1 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading