release/v1.1.2 #23
@@ -66,7 +66,7 @@ Die benötigten Pakete werden vom Installer auf Ubuntu/Debian automatisch instal
|
|||||||
```bash
|
```bash
|
||||||
# Release-Archiv herunterladen und entpacken
|
# Release-Archiv herunterladen und entpacken
|
||||||
curl -fL -o adguard-shield-linux-amd64.tar.gz \
|
curl -fL -o adguard-shield-linux-amd64.tar.gz \
|
||||||
https://git.techniverse.net/scriptos/adguard-shield/releases/download/v1.1.1/adguard-shield-linux-amd64.tar.gz
|
https://git.techniverse.net/scriptos/adguard-shield/releases/download/v1.1.2/adguard-shield-linux-amd64.tar.gz
|
||||||
tar -xzf adguard-shield-linux-amd64.tar.gz
|
tar -xzf adguard-shield-linux-amd64.tar.gz
|
||||||
chmod +x ./adguard-shield
|
chmod +x ./adguard-shield
|
||||||
```
|
```
|
||||||
@@ -155,6 +155,7 @@ sudo adguard-shield <befehl>
|
|||||||
| Befehl | Beschreibung |
|
| Befehl | Beschreibung |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `status` | Aktive Sperren und Konfigurationsübersicht anzeigen |
|
| `status` | Aktive Sperren und Konfigurationsübersicht anzeigen |
|
||||||
|
| `ip-status <IP>` | Status einer einzelnen IP anzeigen |
|
||||||
| `live` / `watch` | Terminal-Live-Ansicht mit Queries, Top-Clients, Sperren und Logs |
|
| `live` / `watch` | Terminal-Live-Ansicht mit Queries, Top-Clients, Sperren und Logs |
|
||||||
| `live --interval 2` | Live-Ansicht mit benutzerdefiniertem Aktualisierungsintervall |
|
| `live --interval 2` | Live-Ansicht mit benutzerdefiniertem Aktualisierungsintervall |
|
||||||
| `live --top 20` | Live-Ansicht mit mehr Top-Einträgen |
|
| `live --top 20` | Live-Ansicht mit mehr Top-Einträgen |
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -90,6 +91,11 @@ func run() error {
|
|||||||
fmt.Printf("Verbindung erfolgreich. %d Querylog-Einträge gefunden.\n", len(items))
|
fmt.Printf("Verbindung erfolgreich. %d Querylog-Einträge gefunden.\n", len(items))
|
||||||
case "status":
|
case "status":
|
||||||
return status(d)
|
return status(d)
|
||||||
|
case "ip-status":
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("Nutzung: adguard-shield ip-status <IP>")
|
||||||
|
}
|
||||||
|
return ipStatus(d, args[0])
|
||||||
case "live", "watch":
|
case "live", "watch":
|
||||||
return liveCommand(ctx, d, args)
|
return liveCommand(ctx, d, args)
|
||||||
case "logs":
|
case "logs":
|
||||||
@@ -269,6 +275,146 @@ func run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ipStatus(d *daemon.Daemon, rawIP string) error {
|
||||||
|
ip := strings.TrimSpace(rawIP)
|
||||||
|
if _, err := netip.ParseAddr(ip); err != nil {
|
||||||
|
return fmt.Errorf("ungültige IP %q", rawIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("IP Status")
|
||||||
|
fmt.Printf("IP: %s\n", ip)
|
||||||
|
|
||||||
|
printIPBanStatus(d, ip)
|
||||||
|
printIPWhitelistStatus(d, ip)
|
||||||
|
printIPOffenseStatus(d, ip)
|
||||||
|
printIPGeoIPCacheStatus(d, ip)
|
||||||
|
return printIPHistory(d, ip, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printIPBanStatus(d *daemon.Daemon, ip string) {
|
||||||
|
b, ok, err := d.Store.BanByIP(ip)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Sperre: Fehler (%v)\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
fmt.Println("Sperre: nein")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Sperre: aktiv")
|
||||||
|
fmt.Printf(" Quelle: %s\n", empty(b.Source, "unbekannt"))
|
||||||
|
fmt.Printf(" Grund: %s\n", empty(b.Reason, "unbekannt"))
|
||||||
|
if b.Domain != "" && b.Domain != "-" {
|
||||||
|
fmt.Printf(" Domain: %s\n", b.Domain)
|
||||||
|
}
|
||||||
|
if b.Count > 0 {
|
||||||
|
fmt.Printf(" Anzahl: %d\n", b.Count)
|
||||||
|
}
|
||||||
|
if b.Protocol != "" && b.Protocol != "-" {
|
||||||
|
fmt.Printf(" Protokoll: %s\n", b.Protocol)
|
||||||
|
}
|
||||||
|
fmt.Printf(" Dauer: %s\n", ipStatusDuration(b.Permanent, b.Duration))
|
||||||
|
if !b.Permanent && b.BanUntil > 0 {
|
||||||
|
until := time.Unix(b.BanUntil, 0)
|
||||||
|
state := until.Format("2006-01-02 15:04:05")
|
||||||
|
if time.Now().Unix() >= b.BanUntil {
|
||||||
|
state += " (abgelaufen, Cleanup ausstehend)"
|
||||||
|
}
|
||||||
|
fmt.Printf(" Ablauf: %s\n", state)
|
||||||
|
}
|
||||||
|
if b.OffenseLevel > 0 {
|
||||||
|
fmt.Printf(" Offense-Stufe bei Sperre: %d\n", b.OffenseLevel)
|
||||||
|
}
|
||||||
|
if b.GeoIPCountry != "" {
|
||||||
|
fmt.Printf(" GeoIP: %s (%s)\n", b.GeoIPCountry, empty(b.GeoIPMode, "unbekannt"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printIPWhitelistStatus(d *daemon.Daemon, ip string) {
|
||||||
|
var hits []string
|
||||||
|
for _, entry := range d.Config.Whitelist {
|
||||||
|
if strings.TrimSpace(entry) == ip {
|
||||||
|
hits = append(hits, "statisch")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wl, ok, err := d.Store.WhitelistByIP(ip)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Whitelist: Fehler (%v)\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
source := empty(wl.Source, "extern")
|
||||||
|
if wl.ResolvedAt != "" {
|
||||||
|
source += ", aufgeloest " + wl.ResolvedAt
|
||||||
|
}
|
||||||
|
hits = append(hits, source)
|
||||||
|
}
|
||||||
|
if len(hits) == 0 {
|
||||||
|
fmt.Println("Whitelist: nein")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Whitelist: ja (%s)\n", strings.Join(hits, "; "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func printIPOffenseStatus(d *daemon.Daemon, ip string) {
|
||||||
|
o, ok, err := d.Store.OffenseByIP(ip)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Offense-Zaehler: Fehler (%v)\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
fmt.Println("Offense-Zaehler: keiner")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state := "aktiv"
|
||||||
|
if o.LastEpoch > 0 && time.Now().Unix()-o.LastEpoch > d.Config.ProgressiveBanResetAfter {
|
||||||
|
state = "abgelaufen"
|
||||||
|
}
|
||||||
|
fmt.Printf("Offense-Zaehler: Stufe %d (%s)\n", o.Level, state)
|
||||||
|
if o.First != "" {
|
||||||
|
fmt.Printf(" Erster Treffer: %s\n", o.First)
|
||||||
|
}
|
||||||
|
if o.Last != "" {
|
||||||
|
fmt.Printf(" Letzter Treffer: %s\n", o.Last)
|
||||||
|
}
|
||||||
|
if d.Config.ProgressiveBanResetAfter > 0 {
|
||||||
|
fmt.Printf(" Reset nach: %ds\n", d.Config.ProgressiveBanResetAfter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printIPGeoIPCacheStatus(d *daemon.Daemon, ip string) {
|
||||||
|
cache, ok, err := d.Store.GeoIPCacheByIP(ip)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("GeoIP-Cache: Fehler (%v)\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
fmt.Println("GeoIP-Cache: kein Eintrag")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("GeoIP-Cache: %s\n", empty(cache.CountryCode, "unbekannt"))
|
||||||
|
if cache.LookedUpAtEpoch > 0 {
|
||||||
|
fmt.Printf(" Nachgeschlagen: %s\n", time.Unix(cache.LookedUpAtEpoch, 0).Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printIPHistory(d *daemon.Daemon, ip string, limit int) error {
|
||||||
|
lines, err := d.Store.RecentHistoryByIP(ip, limit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(lines) == 0 {
|
||||||
|
fmt.Println("History: keine Eintraege")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Printf("History: letzte %d Eintraege\n", len(lines))
|
||||||
|
for _, l := range lines {
|
||||||
|
fmt.Printf(" %s\n", l)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func status(d *daemon.Daemon) error {
|
func status(d *daemon.Daemon) error {
|
||||||
bans, err := d.Store.ActiveBans()
|
bans, err := d.Store.ActiveBans()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -290,11 +436,18 @@ func status(d *daemon.Daemon) error {
|
|||||||
fmt.Printf(" %s | %s | %s | %s\n", b.IP, b.Source, b.Reason, until)
|
fmt.Printf(" %s | %s | %s | %s\n", b.IP, b.Source, b.Reason, until)
|
||||||
}
|
}
|
||||||
if len(bans) > limit {
|
if len(bans) > limit {
|
||||||
fmt.Printf(" ... %d weitere Sperren. Details mit: adguard-shield history oder direkt in SQLite.\n", len(bans)-limit)
|
fmt.Printf(" ... %d weitere Sperren. Details mit: adguard-shield ip-status <IP> oder history.\n", len(bans)-limit)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ipStatusDuration(permanent bool, seconds int64) string {
|
||||||
|
if permanent || seconds == 0 {
|
||||||
|
return "permanent"
|
||||||
|
}
|
||||||
|
return strconv.FormatInt(seconds, 10) + "s"
|
||||||
|
}
|
||||||
|
|
||||||
func blocklistStatus(d *daemon.Daemon) error {
|
func blocklistStatus(d *daemon.Daemon) error {
|
||||||
count, err := d.Store.CountBySource("external-blocklist")
|
count, err := d.Store.CountBySource("external-blocklist")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -472,7 +625,7 @@ Nutzung:
|
|||||||
adguard-shield uninstall [--keep-config]
|
adguard-shield uninstall [--keep-config]
|
||||||
adguard-shield install-status
|
adguard-shield install-status
|
||||||
adguard-shield [-config PATH] run|start|stop|dry-run
|
adguard-shield [-config PATH] run|start|stop|dry-run
|
||||||
adguard-shield status|history [N]|test|flush|ban IP|unban IP|reset-offenses [IP]
|
adguard-shield status|ip-status IP|history [N]|test|flush|ban IP|unban IP|reset-offenses [IP]
|
||||||
adguard-shield live [--interval N] [--top N] [--recent N] [--logs LEVEL] [--once]
|
adguard-shield live [--interval N] [--top N] [--recent N] [--logs LEVEL] [--once]
|
||||||
adguard-shield logs [--level LEVEL] [--limit N]|logs-follow [--level LEVEL]
|
adguard-shield logs [--level LEVEL] [--limit N]|logs-follow [--level LEVEL]
|
||||||
adguard-shield offense-status|offense-cleanup
|
adguard-shield offense-status|offense-cleanup
|
||||||
|
|||||||
BIN
dist/adguard-shield-v1.1.2-linux-amd64
vendored
Normal file
BIN
dist/adguard-shield-v1.1.2-linux-amd64
vendored
Normal file
Binary file not shown.
@@ -58,6 +58,7 @@ sudo systemctl status adguard-shield
|
|||||||
# Diagnose und Monitoring
|
# Diagnose und Monitoring
|
||||||
sudo adguard-shield test
|
sudo adguard-shield test
|
||||||
sudo adguard-shield status
|
sudo adguard-shield status
|
||||||
|
sudo adguard-shield ip-status 192.168.1.100
|
||||||
sudo adguard-shield live
|
sudo adguard-shield live
|
||||||
sudo adguard-shield history 100
|
sudo adguard-shield history 100
|
||||||
sudo adguard-shield logs --level warn --limit 100
|
sudo adguard-shield logs --level warn --limit 100
|
||||||
@@ -360,7 +361,26 @@ Zeigt eine Übersicht des aktuellen Zustands:
|
|||||||
- Externe Whitelist (aktiv/inaktiv, Anzahl URLs)
|
- Externe Whitelist (aktiv/inaktiv, Anzahl URLs)
|
||||||
- Aktive Sperren mit IP, Quelle, Grund und Ablaufzeit
|
- Aktive Sperren mit IP, Quelle, Grund und Ablaufzeit
|
||||||
|
|
||||||
Bei sehr vielen aktiven Sperren werden nur die ersten 50 angezeigt. Für Details nutze `history` oder frage SQLite direkt ab.
|
Bei sehr vielen aktiven Sperren werden nur die ersten 50 angezeigt. Für Details zu einer konkreten Adresse nutze `ip-status <IP>`, für Ereignisse `history`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IP-Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo adguard-shield ip-status 192.168.1.100
|
||||||
|
```
|
||||||
|
|
||||||
|
Zeigt den Status einer einzelnen IP-Adresse:
|
||||||
|
|
||||||
|
- ob eine aktive Sperre existiert
|
||||||
|
- Quelle, Grund, Domain, Protokoll, Dauer und Ablaufzeit der Sperre
|
||||||
|
- statische oder externe Whitelist-Treffer
|
||||||
|
- Offense-Zähler für progressive Sperren
|
||||||
|
- GeoIP-Cache-Eintrag
|
||||||
|
- letzte History-Einträge für diese IP
|
||||||
|
|
||||||
|
Der Befehl ist besonders hilfreich, wenn eine IP nicht in der gekürzten `status`-Übersicht auftaucht oder du prüfen möchtest, warum ein Client gesperrt, nicht gesperrt oder nicht erneut eskaliert wurde.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -250,13 +250,13 @@ ABUSEIPDB_API_KEY="..."
|
|||||||
### Service gestartet
|
### Service gestartet
|
||||||
|
|
||||||
```text
|
```text
|
||||||
AdGuard Shield v1.1.1 wurde auf dns1 gestartet.
|
AdGuard Shield v1.1.2 wurde auf dns1 gestartet.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Service gestoppt
|
### Service gestoppt
|
||||||
|
|
||||||
```text
|
```text
|
||||||
AdGuard Shield v1.1.1 wurde auf dns1 gestoppt.
|
AdGuard Shield v1.1.2 wurde auf dns1 gestoppt.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rate-Limit-Sperre
|
### Rate-Limit-Sperre
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Dieses Dokument hilft beim Eingrenzen typischer Probleme im Betrieb. Die Reihenf
|
|||||||
|
|
||||||
## Erste Diagnose
|
## Erste Diagnose
|
||||||
|
|
||||||
Diese fünf Befehle liefern meistens schon genug Hinweise, um ein Problem einzugrenzen:
|
Diese Befehle liefern meistens schon genug Hinweise, um ein Problem einzugrenzen:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Läuft der Service?
|
# 1. Läuft der Service?
|
||||||
@@ -19,7 +19,10 @@ sudo adguard-shield test
|
|||||||
# 4. Was ist der aktuelle Zustand?
|
# 4. Was ist der aktuelle Zustand?
|
||||||
sudo adguard-shield status
|
sudo adguard-shield status
|
||||||
|
|
||||||
# 5. Gibt es Warnungen oder Fehler?
|
# 5. Was ist zu einer konkreten IP bekannt?
|
||||||
|
sudo adguard-shield ip-status 192.168.1.100
|
||||||
|
|
||||||
|
# 6. Gibt es Warnungen oder Fehler?
|
||||||
sudo adguard-shield logs --level warn --limit 100
|
sudo adguard-shield logs --level warn --limit 100
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -132,6 +135,7 @@ sudo adguard-shield logs --level debug --limit 100
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo adguard-shield status
|
sudo adguard-shield status
|
||||||
|
sudo adguard-shield ip-status 192.168.1.100
|
||||||
sudo adguard-shield history 100
|
sudo adguard-shield history 100
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -536,6 +540,7 @@ Ohne `--keep-config` werden Installationsverzeichnis, State-Verzeichnis und Logd
|
|||||||
| `journalctl -u adguard-shield -n 100` | Systemd-Journal ansehen |
|
| `journalctl -u adguard-shield -n 100` | Systemd-Journal ansehen |
|
||||||
| `test` | API-Verbindung prüfen |
|
| `test` | API-Verbindung prüfen |
|
||||||
| `status` | Aktuellen Zustand und aktive Sperren anzeigen |
|
| `status` | Aktuellen Zustand und aktive Sperren anzeigen |
|
||||||
|
| `ip-status <IP>` | Einzelne IP auf Sperre, Whitelist, Offenses, GeoIP und History prüfen |
|
||||||
| `live` | Echtzeit-Ansicht mit Queries, Sperren und Logs |
|
| `live` | Echtzeit-Ansicht mit Queries, Sperren und Logs |
|
||||||
| `history 100` | Ban-History anzeigen |
|
| `history 100` | Ban-History anzeigen |
|
||||||
| `logs --level warn --limit 100` | Warnungen und Fehler anzeigen |
|
| `logs --level warn --limit 100` | Warnungen und Fehler anzeigen |
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Du brauchst ein fertiges Linux-Binary. Das kann aus einem Release, aus CI oder a
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fL -o adguard-shield-linux-amd64.tar.gz \
|
curl -fL -o adguard-shield-linux-amd64.tar.gz \
|
||||||
https://git.techniverse.net/scriptos/adguard-shield/releases/download/v1.1.1/adguard-shield-linux-amd64.tar.gz
|
https://git.techniverse.net/scriptos/adguard-shield/releases/download/v1.1.2/adguard-shield-linux-amd64.tar.gz
|
||||||
tar -xzf adguard-shield-linux-amd64.tar.gz
|
tar -xzf adguard-shield-linux-amd64.tar.gz
|
||||||
chmod +x ./adguard-shield
|
chmod +x ./adguard-shield
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package appinfo
|
package appinfo
|
||||||
|
|
||||||
var Version = "v1.1.1"
|
var Version = "v1.1.2"
|
||||||
|
|
||||||
const ProjectURL = "https://git.techniverse.net/scriptos/adguard-shield.git"
|
const ProjectURL = "https://git.techniverse.net/scriptos/adguard-shield.git"
|
||||||
|
|||||||
@@ -25,6 +25,27 @@ type Ban struct {
|
|||||||
GeoIPMode string
|
GeoIPMode string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Offense struct {
|
||||||
|
IP string
|
||||||
|
Level int
|
||||||
|
LastEpoch int64
|
||||||
|
Last string
|
||||||
|
First string
|
||||||
|
}
|
||||||
|
|
||||||
|
type WhitelistEntry struct {
|
||||||
|
IP string
|
||||||
|
Source string
|
||||||
|
ResolvedAt string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeoIPCacheEntry struct {
|
||||||
|
IP string
|
||||||
|
CountryCode string
|
||||||
|
LookedUpAtEpoch int64
|
||||||
|
DBMtime int64
|
||||||
|
}
|
||||||
|
|
||||||
type ReportStats struct {
|
type ReportStats struct {
|
||||||
Since int64
|
Since int64
|
||||||
Until int64
|
Until int64
|
||||||
@@ -120,6 +141,22 @@ func (s *Store) BanExists(ip string) (bool, error) {
|
|||||||
return err == nil, err
|
return err == nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) BanByIP(ip string) (Ban, bool, error) {
|
||||||
|
row := s.DB.QueryRow(`SELECT client_ip, COALESCE(domain,''), COALESCE(count,0), COALESCE(ban_until_epoch,0),
|
||||||
|
COALESCE(ban_duration,0), COALESCE(offense_level,0), COALESCE(is_permanent,0), COALESCE(reason,''), COALESCE(protocol,''),
|
||||||
|
COALESCE(source,''), COALESCE(geoip_country,''), COALESCE(geoip_mode,'') FROM active_bans WHERE client_ip=? LIMIT 1`, ip)
|
||||||
|
var b Ban
|
||||||
|
var perm int
|
||||||
|
if err := row.Scan(&b.IP, &b.Domain, &b.Count, &b.BanUntil, &b.Duration, &b.OffenseLevel, &perm, &b.Reason, &b.Protocol, &b.Source, &b.GeoIPCountry, &b.GeoIPMode); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return Ban{}, false, nil
|
||||||
|
}
|
||||||
|
return Ban{}, false, err
|
||||||
|
}
|
||||||
|
b.Permanent = perm == 1
|
||||||
|
return b, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) InsertBan(b Ban) error {
|
func (s *Store) InsertBan(b Ban) error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
perm := 0
|
perm := 0
|
||||||
@@ -259,6 +296,18 @@ func (s *Store) WhitelistContains(ip string) (bool, error) {
|
|||||||
return err == nil, err
|
return err == nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) WhitelistByIP(ip string) (WhitelistEntry, bool, error) {
|
||||||
|
var e WhitelistEntry
|
||||||
|
err := s.DB.QueryRow(`SELECT ip_address, COALESCE(source,''), COALESCE(resolved_at,'') FROM whitelist_cache WHERE ip_address=? LIMIT 1`, ip).Scan(&e.IP, &e.Source, &e.ResolvedAt)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return WhitelistEntry{}, false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return WhitelistEntry{}, false, err
|
||||||
|
}
|
||||||
|
return e, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) ReplaceWhitelist(ips []string, source string) error {
|
func (s *Store) ReplaceWhitelist(ips []string, source string) error {
|
||||||
tx, err := s.DB.Begin()
|
tx, err := s.DB.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -348,6 +397,19 @@ func (s *Store) CountExpiredOffenses(resetAfter int64) (int, error) {
|
|||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) OffenseByIP(ip string) (Offense, bool, error) {
|
||||||
|
var o Offense
|
||||||
|
err := s.DB.QueryRow(`SELECT client_ip, COALESCE(offense_level,0), COALESCE(last_offense_epoch,0), COALESCE(last_offense,''), COALESCE(first_offense,'')
|
||||||
|
FROM offense_tracking WHERE client_ip=? LIMIT 1`, ip).Scan(&o.IP, &o.Level, &o.LastEpoch, &o.Last, &o.First)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return Offense{}, false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return Offense{}, false, err
|
||||||
|
}
|
||||||
|
return o, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) LoadGeoIPCache(ttl, dbMtime int64) (map[string]string, error) {
|
func (s *Store) LoadGeoIPCache(ttl, dbMtime int64) (map[string]string, error) {
|
||||||
rows, err := s.DB.Query(`SELECT ip, country_code FROM geoip_cache WHERE looked_up_at_epoch >= ? AND (db_mtime=? OR db_mtime=0)`, time.Now().Unix()-ttl, dbMtime)
|
rows, err := s.DB.Query(`SELECT ip, country_code FROM geoip_cache WHERE looked_up_at_epoch >= ? AND (db_mtime=? OR db_mtime=0)`, time.Now().Unix()-ttl, dbMtime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -365,6 +427,18 @@ func (s *Store) LoadGeoIPCache(ttl, dbMtime int64) (map[string]string, error) {
|
|||||||
return out, rows.Err()
|
return out, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GeoIPCacheByIP(ip string) (GeoIPCacheEntry, bool, error) {
|
||||||
|
var e GeoIPCacheEntry
|
||||||
|
err := s.DB.QueryRow(`SELECT ip, country_code, COALESCE(looked_up_at_epoch,0), COALESCE(db_mtime,0) FROM geoip_cache WHERE ip=? LIMIT 1`, ip).Scan(&e.IP, &e.CountryCode, &e.LookedUpAtEpoch, &e.DBMtime)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return GeoIPCacheEntry{}, false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return GeoIPCacheEntry{}, false, err
|
||||||
|
}
|
||||||
|
return e, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) UpsertGeoIP(ip, country string, dbMtime int64) error {
|
func (s *Store) UpsertGeoIP(ip, country string, dbMtime int64) error {
|
||||||
_, err := s.DB.Exec(`INSERT OR REPLACE INTO geoip_cache (ip, country_code, looked_up_at_epoch, db_mtime) VALUES (?, ?, ?, ?)`, ip, country, time.Now().Unix(), dbMtime)
|
_, err := s.DB.Exec(`INSERT OR REPLACE INTO geoip_cache (ip, country_code, looked_up_at_epoch, db_mtime) VALUES (?, ?, ?, ?)`, ip, country, time.Now().Unix(), dbMtime)
|
||||||
return err
|
return err
|
||||||
@@ -479,3 +553,21 @@ FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? ORDER BY
|
|||||||
}
|
}
|
||||||
return out, rows.Err()
|
return out, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) RecentHistoryByIP(ip string, limit int) ([]string, error) {
|
||||||
|
rows, err := s.DB.Query(`SELECT timestamp_text, action, client_ip, COALESCE(domain,''), COALESCE(count,''), COALESCE(duration,''), COALESCE(protocol,''), COALESCE(reason,'')
|
||||||
|
FROM ban_history WHERE client_ip=? ORDER BY id DESC LIMIT ?`, ip, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var out []string
|
||||||
|
for rows.Next() {
|
||||||
|
var ts, action, clientIP, domain, count, duration, proto, reason string
|
||||||
|
if err := rows.Scan(&ts, &action, &clientIP, &domain, &count, &duration, &proto, &reason); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s | %s", ts, action, clientIP, domain, count, duration, proto, reason))
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,3 +29,80 @@ func TestStoreBanAndGeoIPCache(t *testing.T) {
|
|||||||
t.Fatalf("unexpected cache: %#v", cache)
|
t.Fatalf("unexpected cache: %#v", cache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStoreIPStatusQueries(t *testing.T) {
|
||||||
|
s, err := Open(filepath.Join(t.TempDir(), "test.db"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ban := Ban{
|
||||||
|
IP: "192.0.2.10",
|
||||||
|
Domain: "example.com",
|
||||||
|
Count: 42,
|
||||||
|
BanUntil: 1234567890,
|
||||||
|
Duration: 600,
|
||||||
|
OffenseLevel: 2,
|
||||||
|
Reason: "rate-limit",
|
||||||
|
Protocol: "dns",
|
||||||
|
Source: "monitor",
|
||||||
|
}
|
||||||
|
if err := s.InsertBan(ban); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
gotBan, ok, err := s.BanByIP("192.0.2.10")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !ok || gotBan.IP != ban.IP || gotBan.Count != ban.Count || gotBan.Reason != ban.Reason {
|
||||||
|
t.Fatalf("unexpected ban lookup: %#v found=%v", gotBan, ok)
|
||||||
|
}
|
||||||
|
if _, ok, err := s.BanByIP("192.0.2.11"); err != nil || ok {
|
||||||
|
t.Fatalf("unexpected missing ban lookup: found=%v err=%v", ok, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.DB.Exec(`INSERT INTO whitelist_cache (ip_address, source) VALUES (?, ?)`, "192.0.2.10", "external"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wl, ok, err := s.WhitelistByIP("192.0.2.10")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !ok || wl.Source != "external" {
|
||||||
|
t.Fatalf("unexpected whitelist lookup: %#v found=%v", wl, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.IncrementOffense("192.0.2.10", 86400); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
offense, ok, err := s.OffenseByIP("192.0.2.10")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !ok || offense.Level != 1 || offense.LastEpoch == 0 {
|
||||||
|
t.Fatalf("unexpected offense lookup: %#v found=%v", offense, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.UpsertGeoIP("192.0.2.10", "DE", 456); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
geo, ok, err := s.GeoIPCacheByIP("192.0.2.10")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !ok || geo.CountryCode != "DE" || geo.DBMtime != 456 {
|
||||||
|
t.Fatalf("unexpected geo lookup: %#v found=%v", geo, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.History("BAN", "192.0.2.10", "example.com", "42", "600s", "dns", "rate-limit"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
history, err := s.RecentHistoryByIP("192.0.2.10", 5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(history) != 1 {
|
||||||
|
t.Fatalf("unexpected history: %#v", history)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user