Skip to content

Appsec support #20

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

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
98 changes: 98 additions & 0 deletions cmd/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package cmd

import (
"time"

"github.com/crowdsecurity/crowdsec-spoa/pkg/metrics"
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/crowdsecurity/go-cs-lib/ptr"
"github.com/prometheus/client_golang/prometheus"
io_prometheus_client "github.com/prometheus/client_model/go"
log "github.com/sirupsen/logrus"
)

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

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

return ""
}

func metricsUpdater(met *models.RemediationComponentsMetrics, updateInterval time.Duration) {
promMetrics, err := prometheus.DefaultGatherer.Gather()

if err != nil {
log.Errorf("failed 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())),
},
})

for _, metricFamily := range promMetrics {
for _, metric := range metricFamily.GetMetric() {
switch metricFamily.GetName() {
case metrics.ActiveDecisionsMetricName:
labels := metric.GetLabel()
value := metric.GetGauge().GetValue()
origin := getLabelValue(labels, "origin")
ipType := getLabelValue(labels, "ip_type")
scope := getLabelValue(labels, "scope")
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,
"scope": scope,
},
Unit: ptr.Of("ip"),
})
case metrics.BlockedRequestMetricName:
labels := metric.GetLabel()
value := metric.GetCounter().GetValue()
origin := getLabelValue(labels, "origin")
ipType := getLabelValue(labels, "ip_type")
remediation := getLabelValue(labels, "remediation")
key := origin + ipType + remediation
log.Debugf("Sending blocked requests for %s %s %s %f | current value: %f | previous value: %f\n", origin, ipType, remediation, value-metrics.LastBlockedRequestValue[key], value, metrics.LastBlockedRequestValue[key])
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
Name: ptr.Of("dropped"),
Value: ptr.Of(value - metrics.LastBlockedRequestValue[key]),
Labels: map[string]string{
"origin": origin,
"ip_type": ipType,
},
Unit: ptr.Of("request"),
})
metrics.LastBlockedRequestValue[key] = value
case metrics.ProcessedRequestMetricName:
labels := metric.GetLabel()
value := metric.GetCounter().GetValue()
origin := getLabelValue(labels, "origin")
ipType := getLabelValue(labels, "ip_type")
key := origin + ipType
log.Debugf("Sending processed requests for %s %s %f | current value: %f | previous value: %f\n", origin, ipType, value-metrics.LastProcessedRequestValue[key], value, metrics.LastProcessedRequestValue[key])
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
Name: ptr.Of("processed"),
Value: ptr.Of(value - metrics.LastProcessedRequestValue[key]),
Labels: map[string]string{
"origin": origin,
"ip_type": ipType,
},
Unit: ptr.Of("request"),
})
metrics.LastProcessedRequestValue[key] = value
}
}
}
}
14 changes: 12 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/crowdsecurity/crowdsec-spoa/pkg/cfg"
"github.com/crowdsecurity/crowdsec-spoa/pkg/dataset"
"github.com/crowdsecurity/crowdsec-spoa/pkg/host"
"github.com/crowdsecurity/crowdsec-spoa/pkg/metrics"
"github.com/crowdsecurity/crowdsec-spoa/pkg/server"
"github.com/crowdsecurity/crowdsec-spoa/pkg/spoa"
csbouncer "github.com/crowdsecurity/go-cs-bouncer"
Expand Down Expand Up @@ -145,10 +146,19 @@ func Execute() error {
return errors.New("bouncer stream halted")
})

if config.PrometheusConfig.Enabled {
metricsProvider, err := csbouncer.NewMetricsProvider(bouncer.APIClient, name, metricsUpdater, log.StandardLogger())

if err != nil {
return fmt.Errorf("failed to create metrics provider: %w", err)
}

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

prometheus.MustRegister(csbouncer.TotalLAPICalls, csbouncer.TotalLAPIError, metrics.TotalActiveDecisions, metrics.TotalBlockedRequests, metrics.TotalProcessedRequests)

if config.PrometheusConfig.Enabled {
go func() {
http.Handle("/metrics", promhttp.Handler())

Expand Down
33 changes: 31 additions & 2 deletions config/crowdsec.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# /etc/haproxy/crowdsec.cfg
[crowdsec]
spoe-agent crowdsec-agent
messages crowdsec-ip crowdsec-http
messages crowdsec-ip crowdsec-http crowdsec-appsec-small-body crowdsec-appsec-large-body

option var-prefix crowdsec
option set-on-error error
Expand All @@ -18,5 +18,34 @@ spoe-message crowdsec-http

## This message should be the first to trigger in the chain
spoe-message crowdsec-ip
args id=unique-id src-ip=src src-port=src_port
args id=unique-id src-ip=src
event on-client-session

spoe-message crowdsec-appsec-small-body
args id=unique-id host=hdr(Host) src-ip=src method=method path=path query=query version=req.ver headers=req.hdrs body=req.body

# Only send the body if it's less than 1M
acl is_small_body req.body_size le 1000000
acl no_remediation var(txn.crowdsec.remediation) -m found
acl send_to_appsec var(txn.crowdsec.send_to_appsec) -m bool true

event on-frontend-http-request #if is_small_body no_remediation send_to_appsec

spoe-message crowdsec-appsec-large-body
args id=unique-id host=hdr(Host) src-ip=src method=method path=path query=query version=req.ver headers=req.hdrs

# Don't send the body if it's more than 1M
acl is_small_body req.body_size le 1000000
acl no_remediation var(txn.crowdsec.remediation) -m found
acl send_to_appsec var(txn.crowdsec.send_to_appsec) -m bool true

event on-frontend-http-request if !is_small_body no_remediation send_to_appsec

#spoe-message crowdsec-ip
# args id=unique-id src-ip=src src-port=src_port
# event on-frontend-http-request

# Use this message if you exposing HAProxy directly to the internet (ie, not behind Cloudflare or any other proxy)
#spoe-message crowdsec-ip
# args id=unique-id src-ip=src src-port=src_port
# event on-frontend-tcp-request
1 change: 1 addition & 0 deletions config/haproxy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ global
lua-load /usr/local/crowdsec/lua/haproxy/crowdsec.lua
setenv CROWDSEC_BAN_TEMPLATE_PATH /var/lib/crowdsec/lua/haproxy/templates/ban.html
setenv CROWDSEC_CAPTCHA_TEMPLATE_PATH /var/lib/crowdsec/lua/haproxy/templates/captcha.html
fd-hard-limit 450

defaults
log global
Expand Down
57 changes: 31 additions & 26 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,61 @@ module github.com/crowdsecurity/crowdsec-spoa
go 1.22.2

require (
github.com/crowdsecurity/crowdsec v1.6.0
github.com/crowdsecurity/go-cs-bouncer v0.0.13
github.com/crowdsecurity/go-cs-lib v0.0.8
github.com/crowdsecurity/crowdsec v1.6.3
github.com/crowdsecurity/go-cs-bouncer v0.0.14
github.com/crowdsecurity/go-cs-lib v0.0.15
github.com/google/uuid v1.6.0
github.com/negasus/haproxy-spoe-go v1.0.5
github.com/oschwald/geoip2-golang v1.9.0
github.com/prometheus/client_golang v1.19.0
github.com/prometheus/client_golang v1.20.4
github.com/prometheus/client_model v0.6.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
golang.org/x/sync v0.7.0
golang.org/x/sync v0.8.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-querystring v1.1.0 // indirect
github.com/josharian/intern v1.0.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/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/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/protobuf v1.32.0 // indirect
github.com/prometheus/common v0.59.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
go.mongodb.org/mongo-driver v1.17.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // 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
)

replace github.com/crowdsecurity/go-cs-bouncer => github.com/LaurenceJJones/go-cs-bouncer v0.0.0-20241011153853-64120470982d
Loading