Bots often send commands via `ssh user@host <command>` (exec request) rather than requesting an interactive shell. These were previously rejected silently. Now exec commands are captured, stored on the session record, and displayed in the web UI session detail page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
129 lines
4.3 KiB
Go
129 lines
4.3 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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)
|
|
|
|
// 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)
|
|
|
|
// 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)
|
|
|
|
// Close releases any resources held by the store.
|
|
Close() error
|
|
}
|