Skip to content

Commit

Permalink
Feat: add support for domain based black lists
Browse files Browse the repository at this point in the history
  • Loading branch information
s-diez committed Jan 16, 2024
1 parent dde773e commit e0480d6
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 25 deletions.
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ run-dev:
--log.debug \
--config.dns-resolver 0.0.0.0:15353

.PHONY: run-dev-domain
run-dev-domain:
go run dnsbl_exporter.go \
--log.debug \
--config.rbls ./rbls-domain.ini \
--config.domain-based \
--config.dns-resolver 0.0.0.0:15353

.PHONY: snapshot
snapshot:
goreleaser build --snapshot --clean
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ $ dnsbl-exporter -h
--config.dns-resolver value IP address of the resolver to use. (default: "127.0.0.1:53")
--config.rbls value Configuration file which contains RBLs (default: "./rbls.ini")
--config.targets value Configuration file which contains the targets to check. (default: "./targets.ini")
--config.domain-based RBLS are domain instead of IP based blacklists (default: false)
--web.listen-address value Address to listen on for web interface and telemetry. (default: ":9211")
--web.telemetry-path value Path under which to expose metrics. (default: "/metrics")
--log.debug Enable more output in the logs, otherwise INFO.
Expand Down Expand Up @@ -85,6 +86,8 @@ luzilla_rbls_ips_blacklisted{hostname="mail.gmx.net",ip="212.227.17.168",rbl="ix

This represent the server's hostname and the DNSBL in question. `0` for unlisted and `1` for listed. Requests to the DNSBL happen in real-time and are not cached. Take this into account and use accordingly.

If the exporter is configured for DNS based blacklists, the ip label represents the return code of the blacklist.

### Caveat

In order to use this, a _proper_ DNS resolver is needed. Proper means: not Google, not Cloudflare, OpenDNS, etc..
Expand Down
14 changes: 10 additions & 4 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ func NewApp(name string, version string) DNSBLApp {
Usage: "Configuration file which contains the targets to check.",
EnvVars: []string{"DNSBL_EXP_TARGETS"},
},
&cli.BoolFlag{
Name: "config.domain-based",
Usage: "RBLS are domain based blacklists.",
Value: false,
},
&cli.StringFlag{
Name: "web.listen-address",
Value: ":9211",
Expand Down Expand Up @@ -169,7 +174,7 @@ func (a *DNSBLApp) Bootstrap() {
return err
}

rblCollector := setup.CreateCollector(rbls, targets, dnsUtil, log.With("area", "metrics"))
rblCollector := setup.CreateCollector(rbls, targets, ctx.Bool("config.domain-based"), dnsUtil, log.With("area", "metrics"))
registry.MustRegister(rblCollector)

registryExporter := setup.CreateRegistry()
Expand All @@ -191,9 +196,10 @@ func (a *DNSBLApp) Bootstrap() {
http.Handle(ctx.String("web.telemetry-path"), mHandler.Handler())

pHandler := prober.ProberHandler{
DNS: dnsUtil,
Rbls: rbls,
Logger: log.With("area", "prober"),
DNS: dnsUtil,
Rbls: rbls,
DomainBased: ctx.Bool("config.domain-based"),
Logger: log.With("area", "prober"),
}
http.Handle("/prober", pHandler)

Expand Down
30 changes: 21 additions & 9 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type RblCollector struct {
rbls []string
util *dns.DNSUtil
targets []string
domainBased bool
logger *slog.Logger
}

Expand All @@ -32,7 +33,7 @@ func BuildFQName(metric string) string {
}

// NewRblCollector ... creates the collector
func NewRblCollector(rbls []string, targets []string, util *dns.DNSUtil, logger *slog.Logger) *RblCollector {
func NewRblCollector(rbls []string, targets []string, domainBased bool, util *dns.DNSUtil, logger *slog.Logger) *RblCollector {
return &RblCollector{
configuredMetric: prometheus.NewDesc(
BuildFQName("used"),
Expand Down Expand Up @@ -70,10 +71,11 @@ func NewRblCollector(rbls []string, targets []string, util *dns.DNSUtil, logger
nil,
nil,
),
rbls: rbls,
util: util,
targets: targets,
logger: logger,
rbls: rbls,
util: util,
targets: targets,
domainBased: domainBased,
logger: logger,
}
}

Expand Down Expand Up @@ -116,7 +118,14 @@ func (c *RblCollector) Collect(ch chan<- prometheus.Metric) {
close(targets)
}()
for _, host := range hosts {
go resolver.Do(host, targets, wg.Done)
if c.domainBased {
go func(hostname string) {
targets <- rbl.Target{Host: hostname}
wg.Done()
}(host)
} else {
go resolver.Do(host, targets, wg.Done)
}
}
// run the check
for target := range targets {
Expand All @@ -143,9 +152,12 @@ func (c *RblCollector) Collect(ch chan<- prometheus.Metric) {
listed.Store(check.Rbl, val.(int)+1)
}

c.logger.Debug("listed?", slog.Int("v", metricValue), slog.String("rbl", check.Rbl))

labelValues := []string{check.Rbl, check.Target.IP.String(), check.Target.Host}
c.logger.Debug("listed?", slog.Int("v", metricValue), slog.String("rbl", check.Rbl), slog.String("reason", check.Text))
ip := ""
if len(check.Target.IP) > 0 {
ip = check.Target.IP.String()
}
labelValues := []string{check.Rbl, ip, check.Target.Host}

// this is an "error" from the RBL/transport
if check.Error {
Expand Down
9 changes: 5 additions & 4 deletions internal/prober/prober.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (
)

type ProberHandler struct {
DNS *dns.DNSUtil
Rbls []string
Logger *slog.Logger
DNS *dns.DNSUtil
Rbls []string
DomainBased bool
Logger *slog.Logger
}

func (p ProberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Expand All @@ -26,7 +27,7 @@ func (p ProberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
targets = append(targets, r.URL.Query().Get("target"))

registry := setup.CreateRegistry()
collector := setup.CreateCollector(p.Rbls, targets, p.DNS, p.Logger)
collector := setup.CreateCollector(p.Rbls, targets, p.DomainBased, p.DNS, p.Logger)
registry.MustRegister(collector)

h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{
Expand Down
4 changes: 2 additions & 2 deletions internal/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"golang.org/x/exp/slog"
)

func CreateCollector(rbls []string, targets []string, dnsUtil *dns.DNSUtil, logger *slog.Logger) *collector.RblCollector {
return collector.NewRblCollector(rbls, targets, dnsUtil, logger)
func CreateCollector(rbls []string, targets []string, domainBased bool, dnsUtil *dns.DNSUtil, logger *slog.Logger) *collector.RblCollector {
return collector.NewRblCollector(rbls, targets, domainBased, dnsUtil, logger)
}

func CreateRegistry() *prometheus.Registry {
Expand Down
7 changes: 7 additions & 0 deletions internal/tests/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ func CreateDNSMock(t *testing.T) *mockdns.Server {
"10.0.0.127.zen.spamhaus.org.": {
TXT: []string{"https://www.spamhaus.org/query/ip/127.0.0.10"},
},
// domain based rbl responses
// https://www.spamhaus.org/faq/section/Spamhaus%20DBL#277
"dbltest.com.dbl.spamhaus.org.": {
A: []string{"127.0.1.2"},
TXT: []string{"https://www.spamhaus.org/query/domain/dbltest.com"},
},
"example.com.dbl.spamhaus.org.": {},
}, true)
if err != nil {
assert.FailNow(t, "failed building mock", "error: %s", err)
Expand Down
28 changes: 22 additions & 6 deletions pkg/rbl/rbl.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,25 @@ func (rbl *RBL) Update(target Target, blocklist string, c chan<- Result) {
slog.String("rbl", blocklist))))
}

func (r *RBL) lookup(blocklist string, ip Target, c chan<- Result, logger *slog.Logger) {
func (r *RBL) lookup(blocklist string, target Target, c chan<- Result, logger *slog.Logger) {
logger.Debug("next up")

result := Result{
Target: ip,
Target: target,
Listed: false,
Rbl: blocklist,
}
domainBased := target.IP == nil

logger.Debug("about to query RBL")

lookup := godnsbl.Reverse(ip.IP) + "." + result.Rbl
var lookup string
if domainBased {
lookup = target.Host + "." + result.Rbl
} else {
lookup = godnsbl.Reverse(target.IP) + "." + result.Rbl
}

logger.Debug("built lookup", slog.String("lookup", lookup))

res, err := r.util.GetARecords(lookup)
Expand All @@ -69,12 +76,12 @@ func (r *RBL) lookup(blocklist string, ip Target, c chan<- Result, logger *slog.
}

if len(res) == 0 {
// ip is not listed
// target (domain or ip) is not listed
c <- result
return
}

logger.Debug("ip is listed")
logger.Debug("target is listed")

result.Listed = true

Expand All @@ -86,9 +93,18 @@ func (r *RBL) lookup(blocklist string, ip Target, c chan<- Result, logger *slog.
c <- result
return
}
if domainBased {
// @see https://datatracker.ietf.org/doc/html/rfc5782
result.Target.IP = reason
}

// fetch (potential) reason
txt, err := r.util.GetTxtRecords(godnsbl.Reverse(reason) + "." + result.Rbl)
var txt []string
if domainBased {
txt, err = r.util.GetTxtRecords(lookup)
} else {
txt, err = r.util.GetTxtRecords(godnsbl.Reverse(reason) + "." + result.Rbl)
}
if err != nil {
logger.Error("error occurred fetching TXT record", slog.String("msg", err.Error()))

Expand Down
35 changes: 35 additions & 0 deletions pkg/rbl/rbl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,41 @@ func TestRblSuite(t *testing.T) {
assert.True(t, result.Listed)
assert.Contains(t, result.Text, "https://www.spamhaus.org/")
})

t.Run("run=domain", func(t *testing.T) {
dnsMock := tests.CreateDNSMock(t)
defer dnsMock.Close()

logger := tests.CreateTestLogger(t)
d := tests.CreateDNSUtil(t, dnsMock.LocalAddr())
r := rbl.New(d, logger)

// https://www.spamhaus.org/faq/section/Spamhaus%20DBL#277
targets := []rbl.Target{
{Host: "dbltest.com"},
{Host: "example.com"},
}

for _, target := range targets {
blocklist := "dbl.spamhaus.org"
c := make(chan rbl.Result)
defer close(c)

r.Update(target, blocklist, c)

res := <-c
assert.False(t, res.Error)
assert.NoError(t, res.ErrorType)
if target.Host == "dbltest.com" {
assert.True(t, res.Listed)
assert.Equal(t, "127.0.1.2", res.Target.IP.String())
assert.Equal(t, res.Text, "https://www.spamhaus.org/query/domain/dbltest.com")
} else {
assert.False(t, res.Listed)
assert.Nil(t, res.Target.IP)
}
}
})
}

func TestResolver(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions rbls-domain.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[rbl]
server=dbl.spamhaus.org
1 change: 1 addition & 0 deletions targets.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ server=smtp.gmail.com
server=mail.gmx.net
server=smtp.yahoo.com
server=smtp.fastmail.com
server=dbltest.com

0 comments on commit e0480d6

Please sign in to comment.