You've already forked caddy-opnsense-blocker
Add background IP intel and restore dashboard stats
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -591,6 +592,51 @@ func (s *Store) GetInvestigation(ctx context.Context, ip string) (model.IPInvest
|
||||
return item, true, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetInvestigationsForIPs(ctx context.Context, ips []string) (map[string]model.IPInvestigation, error) {
|
||||
unique := uniqueNonEmptyStrings(ips)
|
||||
if len(unique) == 0 {
|
||||
return map[string]model.IPInvestigation{}, nil
|
||||
}
|
||||
placeholders := make([]string, len(unique))
|
||||
args := make([]any, 0, len(unique))
|
||||
for index, ip := range unique {
|
||||
placeholders[index] = "?"
|
||||
args = append(args, ip)
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx, fmt.Sprintf(
|
||||
`SELECT ip, payload_json, updated_at FROM ip_investigations WHERE ip IN (%s)`,
|
||||
strings.Join(placeholders, ", "),
|
||||
), args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query ip investigations: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make(map[string]model.IPInvestigation, len(unique))
|
||||
for rows.Next() {
|
||||
var ip string
|
||||
var payload string
|
||||
var updatedAt string
|
||||
if err := rows.Scan(&ip, &payload, &updatedAt); err != nil {
|
||||
return nil, fmt.Errorf("scan ip investigation: %w", err)
|
||||
}
|
||||
var item model.IPInvestigation
|
||||
if err := json.Unmarshal([]byte(payload), &item); err != nil {
|
||||
return nil, fmt.Errorf("decode ip investigation %q: %w", ip, err)
|
||||
}
|
||||
parsed, err := parseTime(updatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse ip investigation updated_at for %q: %w", ip, err)
|
||||
}
|
||||
item.UpdatedAt = parsed
|
||||
items[ip] = item
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate ip investigations: %w", err)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *Store) SaveInvestigation(ctx context.Context, item model.IPInvestigation) error {
|
||||
if item.UpdatedAt.IsZero() {
|
||||
item.UpdatedAt = time.Now().UTC()
|
||||
@@ -616,6 +662,39 @@ func (s *Store) SaveInvestigation(ctx context.Context, item model.IPInvestigatio
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) ListRecentUserAgentsForIP(ctx context.Context, ip string, limit int) ([]string, error) {
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx, `
|
||||
SELECT user_agent
|
||||
FROM events
|
||||
WHERE client_ip = ? AND TRIM(user_agent) <> ''
|
||||
GROUP BY user_agent
|
||||
ORDER BY MAX(occurred_at) DESC, user_agent ASC
|
||||
LIMIT ?`,
|
||||
ip,
|
||||
limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list recent user agents for ip %q: %w", ip, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]string, 0, limit)
|
||||
for rows.Next() {
|
||||
var userAgent string
|
||||
if err := rows.Scan(&userAgent); err != nil {
|
||||
return nil, fmt.Errorf("scan recent user agent for ip %q: %w", ip, err)
|
||||
}
|
||||
items = append(items, userAgent)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate recent user agents for ip %q: %w", ip, err)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetSourceOffset(ctx context.Context, sourceName string) (model.SourceOffset, bool, error) {
|
||||
row := s.db.QueryRowContext(ctx, `SELECT source_name, path, inode, offset, updated_at FROM source_offsets WHERE source_name = ?`, sourceName)
|
||||
var offset model.SourceOffset
|
||||
@@ -1086,6 +1165,24 @@ func boolToInt(value bool) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func uniqueNonEmptyStrings(items []string) []string {
|
||||
seen := make(map[string]struct{}, len(items))
|
||||
result := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
trimmed := strings.TrimSpace(item)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[trimmed]; ok {
|
||||
continue
|
||||
}
|
||||
seen[trimmed] = struct{}{}
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
type queryer interface {
|
||||
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
|
||||
}
|
||||
|
||||
@@ -139,4 +139,18 @@ func TestStoreRecordsEventsAndState(t *testing.T) {
|
||||
if !found || loadedInvestigation.Bot == nil || loadedInvestigation.Bot.Name != "Googlebot" {
|
||||
t.Fatalf("unexpected investigation payload: found=%v investigation=%+v", found, loadedInvestigation)
|
||||
}
|
||||
investigations, err := db.GetInvestigationsForIPs(ctx, []string{event.ClientIP, "198.51.100.99"})
|
||||
if err != nil {
|
||||
t.Fatalf("get investigations for ips: %v", err)
|
||||
}
|
||||
if len(investigations) != 1 || investigations[event.ClientIP].Bot == nil {
|
||||
t.Fatalf("unexpected investigations map: %+v", investigations)
|
||||
}
|
||||
userAgents, err := db.ListRecentUserAgentsForIP(ctx, event.ClientIP, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("list recent user agents: %v", err)
|
||||
}
|
||||
if len(userAgents) != 1 || userAgents[0] != event.UserAgent {
|
||||
t.Fatalf("unexpected user agents: %#v", userAgents)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user