Replace client-side session table filtering with server-side filtering via a new /fragments/recent-sessions htmx endpoint. Add InputBytes column to session tables, Human score > 0 checkbox filter, and Sort by Input Bytes option to help identify sessions with actual shell interaction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
191 lines
6.7 KiB
Go
191 lines
6.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// LoginAttempt represents a deduplicated login attempt.
|
|
type LoginAttempt struct {
|
|
ID int64
|
|
Username string
|
|
Password string
|
|
IP string
|
|
Country string
|
|
Count int
|
|
FirstSeen time.Time
|
|
LastSeen time.Time
|
|
}
|
|
|
|
// Session represents an authenticated SSH session.
|
|
type Session struct {
|
|
ID string
|
|
IP string
|
|
Country string
|
|
Username string
|
|
ShellName string
|
|
ConnectedAt time.Time
|
|
DisconnectedAt *time.Time
|
|
HumanScore *float64
|
|
ExecCommand *string
|
|
EventCount int
|
|
InputBytes int64
|
|
}
|
|
|
|
// SessionLog represents a single log entry for a session.
|
|
type SessionLog struct {
|
|
ID int64
|
|
SessionID string
|
|
Timestamp time.Time
|
|
Input string
|
|
Output string
|
|
}
|
|
|
|
// SessionEvent represents a single I/O event recorded during a session.
|
|
type SessionEvent struct {
|
|
SessionID string
|
|
Timestamp time.Time
|
|
Direction int // 0=input (client→server), 1=output (server→client)
|
|
Data []byte
|
|
}
|
|
|
|
// DashboardStats holds aggregate counts for the web dashboard.
|
|
type DashboardStats struct {
|
|
TotalAttempts int64
|
|
UniqueIPs int64
|
|
TotalSessions int64
|
|
ActiveSessions int64
|
|
}
|
|
|
|
// TimeSeriesPoint represents a single data point in a time series.
|
|
type TimeSeriesPoint struct {
|
|
Timestamp time.Time
|
|
Count int64
|
|
}
|
|
|
|
// HourlyCount represents the total attempts for a given hour of day.
|
|
type HourlyCount struct {
|
|
Hour int // 0-23
|
|
Count int64
|
|
}
|
|
|
|
// CountryCount represents the total attempts from a given country.
|
|
type CountryCount struct {
|
|
Country string
|
|
Count int64
|
|
}
|
|
|
|
// DashboardFilter contains optional filters for dashboard queries.
|
|
type DashboardFilter struct {
|
|
Since *time.Time
|
|
Until *time.Time
|
|
IP string
|
|
Country string
|
|
Username string
|
|
HumanScoreAboveZero bool
|
|
SortBy string
|
|
}
|
|
|
|
// TopEntry represents a value and its count for top-N queries.
|
|
type TopEntry struct {
|
|
Value string
|
|
Country string // populated by GetTopIPs
|
|
Count int64
|
|
}
|
|
|
|
// 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, country string) error
|
|
|
|
// CreateSession creates a new session record and returns its UUID.
|
|
CreateSession(ctx context.Context, ip, username, shellName, country 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
|
|
|
|
// SetExecCommand sets the exec command for a session.
|
|
SetExecCommand(ctx context.Context, sessionID string, command string) 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)
|
|
|
|
// GetDashboardStats returns aggregate counts for the dashboard.
|
|
GetDashboardStats(ctx context.Context) (*DashboardStats, error)
|
|
|
|
// GetTopUsernames returns the top N usernames by total attempt count.
|
|
GetTopUsernames(ctx context.Context, limit int) ([]TopEntry, error)
|
|
|
|
// GetTopPasswords returns the top N passwords by total attempt count.
|
|
GetTopPasswords(ctx context.Context, limit int) ([]TopEntry, error)
|
|
|
|
// GetTopIPs returns the top N IPs by total attempt count.
|
|
GetTopIPs(ctx context.Context, limit int) ([]TopEntry, error)
|
|
|
|
// GetTopCountries returns the top N countries by total attempt count.
|
|
GetTopCountries(ctx context.Context, limit int) ([]TopEntry, error)
|
|
|
|
// GetTopExecCommands returns the top N exec commands by session count.
|
|
GetTopExecCommands(ctx context.Context, limit int) ([]TopEntry, error)
|
|
|
|
// GetRecentSessions returns the most recent sessions ordered by connected_at DESC.
|
|
// If activeOnly is true, only sessions with no disconnected_at are returned.
|
|
GetRecentSessions(ctx context.Context, limit int, activeOnly bool) ([]Session, error)
|
|
|
|
// GetFilteredSessions returns sessions matching the given filter, ordered
|
|
// by the filter's SortBy field (default: connected_at DESC).
|
|
GetFilteredSessions(ctx context.Context, limit int, activeOnly bool, f DashboardFilter) ([]Session, error)
|
|
|
|
// GetSession returns a single session by ID.
|
|
GetSession(ctx context.Context, sessionID string) (*Session, error)
|
|
|
|
// GetSessionLogs returns all log entries for a session ordered by timestamp.
|
|
GetSessionLogs(ctx context.Context, sessionID string) ([]SessionLog, error)
|
|
|
|
// AppendSessionEvents batch-inserts session events.
|
|
AppendSessionEvents(ctx context.Context, events []SessionEvent) error
|
|
|
|
// GetSessionEvents returns all events for a session ordered by id.
|
|
GetSessionEvents(ctx context.Context, sessionID string) ([]SessionEvent, error)
|
|
|
|
// CloseActiveSessions sets disconnected_at for all sessions that are
|
|
// still marked as active. This should be called at startup to clean up
|
|
// sessions left over from a previous unclean shutdown.
|
|
CloseActiveSessions(ctx context.Context, disconnectedAt time.Time) (int64, error)
|
|
|
|
// GetAttemptsOverTime returns daily attempt counts for the last N days.
|
|
GetAttemptsOverTime(ctx context.Context, days int, since, until *time.Time) ([]TimeSeriesPoint, error)
|
|
|
|
// GetHourlyPattern returns total attempts grouped by hour of day (0-23).
|
|
GetHourlyPattern(ctx context.Context, since, until *time.Time) ([]HourlyCount, error)
|
|
|
|
// GetCountryStats returns total attempts per country, ordered by count DESC.
|
|
GetCountryStats(ctx context.Context) ([]CountryCount, error)
|
|
|
|
// GetFilteredDashboardStats returns aggregate counts with optional filters applied.
|
|
GetFilteredDashboardStats(ctx context.Context, f DashboardFilter) (*DashboardStats, error)
|
|
|
|
// GetFilteredTopUsernames returns top usernames with optional filters applied.
|
|
GetFilteredTopUsernames(ctx context.Context, limit int, f DashboardFilter) ([]TopEntry, error)
|
|
|
|
// GetFilteredTopPasswords returns top passwords with optional filters applied.
|
|
GetFilteredTopPasswords(ctx context.Context, limit int, f DashboardFilter) ([]TopEntry, error)
|
|
|
|
// GetFilteredTopIPs returns top IPs with optional filters applied.
|
|
GetFilteredTopIPs(ctx context.Context, limit int, f DashboardFilter) ([]TopEntry, error)
|
|
|
|
// GetFilteredTopCountries returns top countries with optional filters applied.
|
|
GetFilteredTopCountries(ctx context.Context, limit int, f DashboardFilter) ([]TopEntry, error)
|
|
|
|
// Close releases any resources held by the store.
|
|
Close() error
|
|
}
|