aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2025-02-14 13:39:34 -0500
committerXe Iaso <me@xeiaso.net>2025-02-14 13:39:34 -0500
commit1acc1602f2ff678c0e4bf49fa62345505ec0aa35 (patch)
treed2eec95941c9cd0a53f6de89cfde44222aa55940 /cmd
parent17c44496aaa77e8d0a499db8c044cb42ab00086b (diff)
downloadx-1acc1602f2ff678c0e4bf49fa62345505ec0aa35.tar.xz
x-1acc1602f2ff678c0e4bf49fa62345505ec0aa35.zip
cmd/anubis: enable DNSBL checking via dronebl
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/anubis/botPolicies.json3
-rw-r--r--cmd/anubis/internal/config/config.go3
-rw-r--r--cmd/anubis/internal/dnsbl/dnsbl.go95
-rw-r--r--cmd/anubis/internal/dnsbl/dnsbl_test.go55
-rw-r--r--cmd/anubis/internal/dnsbl/droneblresponse_string.go54
-rw-r--r--cmd/anubis/main.go14
-rw-r--r--cmd/anubis/policy.go5
7 files changed, 226 insertions, 3 deletions
diff --git a/cmd/anubis/botPolicies.json b/cmd/anubis/botPolicies.json
index 8ca88e1..b90eba0 100644
--- a/cmd/anubis/botPolicies.json
+++ b/cmd/anubis/botPolicies.json
@@ -60,5 +60,6 @@
"user_agent_regex": "Mozilla",
"action": "CHALLENGE"
}
- ]
+ ],
+ "dnsbl": true
} \ No newline at end of file
diff --git a/cmd/anubis/internal/config/config.go b/cmd/anubis/internal/config/config.go
index 7f986c1..b67db77 100644
--- a/cmd/anubis/internal/config/config.go
+++ b/cmd/anubis/internal/config/config.go
@@ -53,5 +53,6 @@ func (b Bot) Valid() error {
}
type Config struct {
- Bots []Bot `json:"bots"`
+ Bots []Bot `json:"bots"`
+ DNSBL bool `json:"dnsbl"`
}
diff --git a/cmd/anubis/internal/dnsbl/dnsbl.go b/cmd/anubis/internal/dnsbl/dnsbl.go
new file mode 100644
index 0000000..60edd5c
--- /dev/null
+++ b/cmd/anubis/internal/dnsbl/dnsbl.go
@@ -0,0 +1,95 @@
+package dnsbl
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "strings"
+)
+
+//go:generate go tool golang.org/x/tools/cmd/stringer -type=DroneBLResponse
+
+type DroneBLResponse byte
+
+const (
+ AllGood DroneBLResponse = 0
+ IRCDrone DroneBLResponse = 3
+ Bottler DroneBLResponse = 5
+ UnknownSpambotOrDrone DroneBLResponse = 6
+ DDOSDrone DroneBLResponse = 7
+ SOCKSProxy DroneBLResponse = 8
+ HTTPProxy DroneBLResponse = 9
+ ProxyChain DroneBLResponse = 10
+ OpenProxy DroneBLResponse = 11
+ OpenDNSResolver DroneBLResponse = 12
+ BruteForceAttackers DroneBLResponse = 13
+ OpenWingateProxy DroneBLResponse = 14
+ CompromisedRouter DroneBLResponse = 15
+ AutoRootingWorms DroneBLResponse = 16
+ AutoDetectedBotIP DroneBLResponse = 17
+ Unknown DroneBLResponse = 255
+)
+
+func Reverse(ip net.IP) string {
+ if ip.To4() != nil {
+ return reverse4(ip)
+ }
+
+ return reverse6(ip)
+}
+
+func reverse4(ip net.IP) string {
+ splitAddress := strings.Split(ip.String(), ".")
+
+ // swap first and last octet
+ splitAddress[0], splitAddress[3] = splitAddress[3], splitAddress[0]
+ // swap middle octets
+ splitAddress[1], splitAddress[2] = splitAddress[2], splitAddress[1]
+
+ return strings.Join(splitAddress, ".")
+}
+
+func reverse6(ip net.IP) string {
+ ipBytes := []byte(ip)
+ var sb strings.Builder
+
+ for i := len(ipBytes) - 1; i >= 0; i-- {
+ // Split the byte into two nibbles
+ highNibble := ipBytes[i] >> 4
+ lowNibble := ipBytes[i] & 0x0F
+
+ // Append the nibbles in reversed order
+ sb.WriteString(fmt.Sprintf("%x.%x.", lowNibble, highNibble))
+ }
+
+ return sb.String()[:len(sb.String())-1]
+}
+
+func Lookup(ipStr string) (DroneBLResponse, error) {
+ ip := net.ParseIP(ipStr)
+ if ip == nil {
+ return Unknown, errors.New("dnsbl: input is not an IP address")
+ }
+
+ revIP := Reverse(ip) + ".dnsbl.dronebl.org"
+
+ ips, err := net.LookupIP(revIP)
+ if err != nil {
+ var dnserr *net.DNSError
+ if errors.As(err, &dnserr) {
+ if dnserr.IsNotFound {
+ return AllGood, nil
+ }
+ }
+
+ return Unknown, err
+ }
+
+ if len(ips) != 0 {
+ for _, ip := range ips {
+ return DroneBLResponse(ip.To4()[3]), nil
+ }
+ }
+
+ return UnknownSpambotOrDrone, nil
+}
diff --git a/cmd/anubis/internal/dnsbl/dnsbl_test.go b/cmd/anubis/internal/dnsbl/dnsbl_test.go
new file mode 100644
index 0000000..9bcf0e7
--- /dev/null
+++ b/cmd/anubis/internal/dnsbl/dnsbl_test.go
@@ -0,0 +1,55 @@
+package dnsbl
+
+import (
+ "fmt"
+ "net"
+ "testing"
+)
+
+func TestReverse4(t *testing.T) {
+ cases := []struct {
+ inp, out string
+ }{
+ {"1.2.3.4", "4.3.2.1"},
+ }
+
+ for _, cs := range cases {
+ t.Run(fmt.Sprintf("%s->%s", cs.inp, cs.out), func(t *testing.T) {
+ out := reverse4(net.ParseIP(cs.inp))
+
+ if out != cs.out {
+ t.Errorf("wanted %s\ngot: %s", cs.out, out)
+ }
+ })
+ }
+}
+
+func TestReverse6(t *testing.T) {
+ cases := []struct {
+ inp, out string
+ }{
+ {
+ inp: "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0",
+ out: "0.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1",
+ },
+ }
+
+ for _, cs := range cases {
+ t.Run(fmt.Sprintf("%s->%s", cs.inp, cs.out), func(t *testing.T) {
+ out := reverse6(net.ParseIP(cs.inp))
+
+ if out != cs.out {
+ t.Errorf("wanted %s, got: %s", cs.out, out)
+ }
+ })
+ }
+}
+
+func TestLookup(t *testing.T) {
+ resp, err := Lookup("27.65.243.194")
+ if err != nil {
+ t.Fatalf("it broked: %v", err)
+ }
+
+ t.Logf("response: %x", resp)
+} \ No newline at end of file
diff --git a/cmd/anubis/internal/dnsbl/droneblresponse_string.go b/cmd/anubis/internal/dnsbl/droneblresponse_string.go
new file mode 100644
index 0000000..5104dda
--- /dev/null
+++ b/cmd/anubis/internal/dnsbl/droneblresponse_string.go
@@ -0,0 +1,54 @@
+// Code generated by "stringer -type=DroneBLResponse"; DO NOT EDIT.
+
+package dnsbl
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[AllGood-0]
+ _ = x[IRCDrone-3]
+ _ = x[Bottler-5]
+ _ = x[UnknownSpambotOrDrone-6]
+ _ = x[DDOSDrone-7]
+ _ = x[SOCKSProxy-8]
+ _ = x[HTTPProxy-9]
+ _ = x[ProxyChain-10]
+ _ = x[OpenProxy-11]
+ _ = x[OpenDNSResolver-12]
+ _ = x[BruteForceAttackers-13]
+ _ = x[OpenWingateProxy-14]
+ _ = x[CompromisedRouter-15]
+ _ = x[AutoRootingWorms-16]
+ _ = x[AutoDetectedBotIP-17]
+ _ = x[Unknown-255]
+}
+
+const (
+ _DroneBLResponse_name_0 = "AllGood"
+ _DroneBLResponse_name_1 = "IRCDrone"
+ _DroneBLResponse_name_2 = "BottlerUnknownSpambotOrDroneDDOSDroneSOCKSProxyHTTPProxyProxyChainOpenProxyOpenDNSResolverBruteForceAttackersOpenWingateProxyCompromisedRouterAutoRootingWormsAutoDetectedBotIP"
+ _DroneBLResponse_name_3 = "Unknown"
+)
+
+var (
+ _DroneBLResponse_index_2 = [...]uint8{0, 7, 28, 37, 47, 56, 66, 75, 90, 109, 125, 142, 158, 175}
+)
+
+func (i DroneBLResponse) String() string {
+ switch {
+ case i == 0:
+ return _DroneBLResponse_name_0
+ case i == 3:
+ return _DroneBLResponse_name_1
+ case 5 <= i && i <= 17:
+ i -= 5
+ return _DroneBLResponse_name_2[_DroneBLResponse_index_2[i]:_DroneBLResponse_index_2[i+1]]
+ case i == 255:
+ return _DroneBLResponse_name_3
+ default:
+ return "DroneBLResponse(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+}
diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go
index a15dba8..477fe50 100644
--- a/cmd/anubis/main.go
+++ b/cmd/anubis/main.go
@@ -30,6 +30,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"within.website/x"
"within.website/x/cmd/anubis/internal/config"
+ "within.website/x/cmd/anubis/internal/dnsbl"
"within.website/x/internal"
"within.website/x/xess"
)
@@ -213,6 +214,19 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request) {
)
policyApplications.WithLabelValues(cr.Name, string(cr.Rule)).Add(1)
+ ip := r.Header.Get("X-Real-Ip")
+
+ if s.policy.DNSBL && ip != "" {
+ resp, err := dnsbl.Lookup(ip)
+ if err != nil {
+ lg.Error("can't look up ip in dnsbl", "err", err)
+ } else {
+ lg.Info("DNSBL hit", "status", resp.String())
+ templ.Handler(base("Oh noes!", errorPage(fmt.Sprintf("DroneBL reported an entry: %s, see https://dronebl.org/lookup?ip=%s", resp.String(), ip))), templ.WithStatus(http.StatusOK)).ServeHTTP(w, r)
+ return
+ }
+ }
+
switch cr.Rule {
case config.RuleAllow:
lg.Debug("allowing traffic to origin (explicit)")
diff --git a/cmd/anubis/policy.go b/cmd/anubis/policy.go
index 7d778c7..6e2241e 100644
--- a/cmd/anubis/policy.go
+++ b/cmd/anubis/policy.go
@@ -24,7 +24,8 @@ var (
type ParsedConfig struct {
orig config.Config
- Bots []Bot
+ Bots []Bot
+ DNSBL bool
}
type Bot struct {
@@ -85,6 +86,8 @@ func parseConfig(fin io.Reader, fname string) (*ParsedConfig, error) {
return nil, fmt.Errorf("errors validating policy config JSON %s: %w", fname, err)
}
+ result.DNSBL = c.DNSBL
+
return result, nil
}