feat: add SQLite storage for login attempts and sessions

Adds persistent storage using modernc.org/sqlite (pure Go). Login
attempts are deduplicated by (username, password, ip) with counts.
Sessions and session logs are tracked with UUID IDs. Includes embedded
SQL migrations, configurable retention with background pruning, and
an in-memory store for tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 17:33:45 +01:00
parent 75bac814d4
commit d655968216
21 changed files with 1131 additions and 10 deletions

63
internal/storage/store.go Normal file
View File

@@ -0,0 +1,63 @@
package storage
import (
"context"
"time"
)
// LoginAttempt represents a deduplicated login attempt.
type LoginAttempt struct {
ID int64
Username string
Password string
IP string
Count int
FirstSeen time.Time
LastSeen time.Time
}
// Session represents an authenticated SSH session.
type Session struct {
ID string
IP string
Username string
ShellName string
ConnectedAt time.Time
DisconnectedAt *time.Time
HumanScore *float64
}
// SessionLog represents a single log entry for a session.
type SessionLog struct {
ID int64
SessionID string
Timestamp time.Time
Input string
Output string
}
// Store is the interface for persistent storage of honeypot data.
type Store interface {
// RecordLoginAttempt upserts a login attempt, incrementing the count
// for existing (username, password, ip) combinations.
RecordLoginAttempt(ctx context.Context, username, password, ip string) error
// CreateSession creates a new session record and returns its UUID.
CreateSession(ctx context.Context, ip, username, shellName string) (string, error)
// EndSession sets the disconnected_at timestamp for a session.
EndSession(ctx context.Context, sessionID string, disconnectedAt time.Time) error
// UpdateHumanScore sets the human detection score for a session.
UpdateHumanScore(ctx context.Context, sessionID string, score float64) error
// AppendSessionLog adds a log entry to a session.
AppendSessionLog(ctx context.Context, sessionID, input, output string) error
// DeleteRecordsBefore removes all records older than the given cutoff
// and returns the total number of deleted rows.
DeleteRecordsBefore(ctx context.Context, cutoff time.Time) (int64, error)
// Close releases any resources held by the store.
Close() error
}