feat: add GeoIP country lookup with embedded DB-IP Lite database (PLAN.md 4.3)
Embeds a DB-IP Lite country MMDB (~5MB) in the binary via go:embed, keeping the single-binary deployment story clean. Country codes are stored alongside login attempts and sessions, shown in the dashboard (Top IPs, Top Countries card, Recent/Active Sessions, session detail). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"git.t-juice.club/torjus/oubliette/internal/auth"
|
||||
"git.t-juice.club/torjus/oubliette/internal/config"
|
||||
"git.t-juice.club/torjus/oubliette/internal/detection"
|
||||
"git.t-juice.club/torjus/oubliette/internal/geoip"
|
||||
"git.t-juice.club/torjus/oubliette/internal/metrics"
|
||||
"git.t-juice.club/torjus/oubliette/internal/notify"
|
||||
"git.t-juice.club/torjus/oubliette/internal/shell"
|
||||
@@ -37,6 +38,7 @@ type Server struct {
|
||||
shellRegistry *shell.Registry
|
||||
notifier notify.Sender
|
||||
metrics *metrics.Metrics
|
||||
geoip *geoip.Reader
|
||||
}
|
||||
|
||||
func New(cfg config.Config, store storage.Store, logger *slog.Logger, m *metrics.Metrics) (*Server, error) {
|
||||
@@ -57,6 +59,11 @@ func New(cfg config.Config, store storage.Store, logger *slog.Logger, m *metrics
|
||||
return nil, fmt.Errorf("registering cisco shell: %w", err)
|
||||
}
|
||||
|
||||
geo, err := geoip.New()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening geoip database: %w", err)
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
cfg: cfg,
|
||||
store: store,
|
||||
@@ -66,6 +73,7 @@ func New(cfg config.Config, store storage.Store, logger *slog.Logger, m *metrics
|
||||
shellRegistry: registry,
|
||||
notifier: notify.NewSender(cfg.Notify.Webhooks, logger),
|
||||
metrics: m,
|
||||
geoip: geo,
|
||||
}
|
||||
|
||||
hostKey, err := loadOrGenerateHostKey(cfg.SSH.HostKeyPath)
|
||||
@@ -83,6 +91,8 @@ func New(cfg config.Config, store storage.Store, logger *slog.Logger, m *metrics
|
||||
}
|
||||
|
||||
func (s *Server) ListenAndServe(ctx context.Context) error {
|
||||
defer s.geoip.Close()
|
||||
|
||||
listener, err := net.Listen("tcp", s.cfg.SSH.ListenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen: %w", err)
|
||||
@@ -185,8 +195,9 @@ func (s *Server) handleSession(channel ssh.Channel, requests <-chan *ssh.Request
|
||||
}
|
||||
|
||||
ip := extractIP(conn.RemoteAddr())
|
||||
country := s.geoip.Lookup(ip)
|
||||
sessionStart := time.Now()
|
||||
sessionID, err := s.store.CreateSession(context.Background(), ip, conn.User(), selectedShell.Name())
|
||||
sessionID, err := s.store.CreateSession(context.Background(), ip, conn.User(), selectedShell.Name(), country)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to create session", "err", err)
|
||||
} else {
|
||||
@@ -350,7 +361,8 @@ func (s *Server) passwordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.
|
||||
"reason", d.Reason,
|
||||
)
|
||||
|
||||
if err := s.store.RecordLoginAttempt(context.Background(), conn.User(), string(password), ip); err != nil {
|
||||
country := s.geoip.Lookup(ip)
|
||||
if err := s.store.RecordLoginAttempt(context.Background(), conn.User(), string(password), ip, country); err != nil {
|
||||
s.logger.Error("failed to record login attempt", "err", err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user