This repository has been archived on 2026-03-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
oubliette/internal/storage/store.go
Torjus Håkestad 0133d956a5 feat: capture SSH exec commands (PLAN.md 4.4)
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>
2026-02-15 17:43:11 +01:00

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
}