From bc9c5dbe0e87ee32bfa7f5df9537f98ca0ce43d0 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 21 Oct 2021 10:33:28 +0200 Subject: [PATCH] Add ports listener feature --- apiary.toml | 17 ++++++ cmd/apiary.go | 22 ++++++++ config/config.go | 8 +++ honeypot/ports/postgresstore.go | 71 ++++++++++++++++++++++++ honeypot/ports/server.go | 96 +++++++++++++++++++++++++++++++++ honeypot/ports/server_test.go | 45 ++++++++++++++++ honeypot/ports/store.go | 26 +++++++++ version.go | 2 +- 8 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 honeypot/ports/postgresstore.go create mode 100644 honeypot/ports/server.go create mode 100644 honeypot/ports/server_test.go create mode 100644 honeypot/ports/store.go diff --git a/apiary.toml b/apiary.toml index 27197be..c45375a 100644 --- a/apiary.toml +++ b/apiary.toml @@ -59,3 +59,20 @@ CacheDir = "/var/apiary/certs" # Redirect HTTP to HTTPS # Default: true RedirectHTTP = true + +[Ports] +# Enable the port listener. +# Default: false +Enable = true + +# Which address to listen on. +# Default: "" (listen to all addresses) +Addr = "" + +# Which TCP ports to listen to. +# Default: [] +TCPPorts = ["25"] + +# Which UDP ports to listen to. +# Default: [] +UDPPorts = ["25"] diff --git a/cmd/apiary.go b/cmd/apiary.go index 2d2d503..df833f8 100644 --- a/cmd/apiary.go +++ b/cmd/apiary.go @@ -12,6 +12,7 @@ import ( "github.com/urfave/cli/v2" "github.uio.no/torjus/apiary" "github.uio.no/torjus/apiary/config" + "github.uio.no/torjus/apiary/honeypot/ports" "github.uio.no/torjus/apiary/honeypot/ssh" "github.uio.no/torjus/apiary/honeypot/ssh/store" "github.uio.no/torjus/apiary/web" @@ -113,6 +114,25 @@ func ActionServe(c *cli.Context) error { rootCtx, rootCancel := context.WithCancel(c.Context) serversCtx, serversCancel := context.WithCancel(rootCtx) + // Setup portlistener, if configured + if cfg.Ports.Enable { + portsCtx, cancel := context.WithCancel(rootCtx) + defer cancel() + + // TODO: Add more stores + store := &ports.MemoryStore{} + portsServer := ports.New(store) + portsServer.Logger = loggers.portsLogger + for _, port := range cfg.Ports.TCPPorts { + portsServer.AddTCPPort(port) + } + + go func() { + loggers.rootLogger.Info("Starting ports server") + portsServer.Start(portsCtx) + }() + } + // Handle interrupt go func() { <-interruptChan @@ -171,6 +191,7 @@ type loggerCollection struct { honeypotLogger *zap.SugaredLogger webAccessLogger *zap.SugaredLogger webServerLogger *zap.SugaredLogger + portsLogger *zap.SugaredLogger } func setupLoggers(cfg config.Config) *loggerCollection { @@ -198,6 +219,7 @@ func setupLoggers(cfg config.Config) *loggerCollection { honeypotLogger: rootLogger.Named("HON").Sugar(), webAccessLogger: rootLogger.Named("ACC").Sugar(), webServerLogger: rootLogger.Named("WEB").Sugar(), + portsLogger: rootLogger.Named("PRT").Sugar(), } } diff --git a/config/config.go b/config/config.go index fe652dc..2b4e23a 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,7 @@ type Config struct { Store StoreConfig `toml:"Store"` Honeypot HoneypotConfig `toml:"Honeypot"` Frontend FrontendConfig `toml:"Frontend"` + Ports PortsConfig `toml:"Ports"` } type StoreConfig struct { Type string `toml:"Type"` @@ -46,6 +47,13 @@ type FrontendAutocertConfig struct { RedirectHTTP bool `toml:"RedirectHTTP"` } +type PortsConfig struct { + Enable bool `toml:"Enable"` + Addr string `toml:"Addr"` + TCPPorts []string `toml:"TCPPorts"` + UDPPorts []string `toml:"UDPPorts"` +} + func FromReader(r io.Reader) (Config, error) { var c Config diff --git a/honeypot/ports/postgresstore.go b/honeypot/ports/postgresstore.go new file mode 100644 index 0000000..823cfe3 --- /dev/null +++ b/honeypot/ports/postgresstore.go @@ -0,0 +1,71 @@ +package ports + +import ( + "database/sql" + "time" + + _ "github.com/jackc/pgx/v4/stdlib" +) + +const DBSchema = ` +CREATE TABLE IF NOT EXISTS port_attempts( + id serial PRIMARY KEY, + date timestamptz, + remote_ip inet, + port varchar(5) +);` + +type PostgresStore struct { + db *sql.DB +} + +func NewPostgresStore(dsn string) (*PostgresStore, error) { + db, err := sql.Open("pgx", dsn) + if err != nil { + return nil, err + } + + s := &PostgresStore{db: db} + return s, nil +} + +func (s *PostgresStore) InitDB() error { + _, err := s.db.Exec(DBSchema) + return err +} + +func (s *PostgresStore) Add(attempt *ConnectionAttempt) error { + stmt := `INSERT INTO port_attempts(date, remote_ip, port) + VALUES ($1, $2, $3)` + tx, err := s.db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + _, err = tx.Exec(stmt, time.Now(), attempt.From, attempt.Port) + if err != nil { + return err + } + return tx.Commit() +} + +func (s *PostgresStore) List() ([]*ConnectionAttempt, error) { + stmt := `SELECT remote_ip, port FROM port_attempts` + rows, err := s.db.Query(stmt) + if err != nil { + return nil, err + } + defer rows.Close() + + var attempts []*ConnectionAttempt + for rows.Next() { + var a ConnectionAttempt + if err := rows.Scan(&a.From, &a.Port); err != nil { + return nil, err + } + attempts = append(attempts, &a) + } + + return attempts, nil +} diff --git a/honeypot/ports/server.go b/honeypot/ports/server.go new file mode 100644 index 0000000..3da306b --- /dev/null +++ b/honeypot/ports/server.go @@ -0,0 +1,96 @@ +package ports + +import ( + "context" + "fmt" + "net" + "time" + + "go.uber.org/zap" +) + +type Server struct { + EnabledPortsTCP []string + EnabledPortsUDP []string + IP string + Logger *zap.SugaredLogger + + store Store +} + +func New(store Store) *Server { + return &Server{store: store, Logger: zap.NewNop().Sugar()} +} + +func (s *Server) Start(ctx context.Context) error { + for _, port := range s.EnabledPortsTCP { + portCtx, cancel := context.WithCancel(ctx) + go s.doListenTCP(portCtx, port) + defer cancel() + } + + <-ctx.Done() + return nil +} + +func (s *Server) AddTCPPort(port string) { + s.EnabledPortsTCP = append(s.EnabledPortsTCP, port) +} + +func (s *Server) doListenTCP(ctx context.Context, port string) error { + lAddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(s.IP, port)) + if err != nil { + s.Logger.Warnw("Error resoling listening addr.", "network", "tcp", "port", port) + return fmt.Errorf("error resolving listening address: %w", err) + } + + listener, err := net.ListenTCP("tcp", lAddr) + if err != nil { + return fmt.Errorf("error starting listener: %w", err) + } + defer listener.Close() + + go func() { + <-ctx.Done() + s.Logger.Debug("Listener context cancelled.") + listener.Close() + }() + + s.Logger.Infow("Listening for connections to TCP port.", "port", port) + for { + conn, err := listener.Accept() + if err != nil { + select { + case <-ctx.Done(): + s.Logger.Infow("Listener shutting down.", "port", port, "network", "tcp") + return nil + default: + s.Logger.Infow("Error accepting connection.", "error", err) + continue + } + } + + s.Logger.Infow("Got connection on port.", "port", port, "network", "tcp", "remote_addr", conn.RemoteAddr().String()) + + conn.SetReadDeadline(time.Now().Add(time.Second * 15)) + + buf := make([]byte, 256) + + _, err = conn.Read(buf) + if err != nil { + buf = []byte{} + } + + attempt := &ConnectionAttempt{ + Port: port, + Network: "tcp", + From: conn.RemoteAddr().String(), + Data: buf, + } + conn.Close() + + if err := s.store.Add(attempt); err != nil { + s.Logger.Warnw("Error storing attempt in store.", "error", err) + } + } +} diff --git a/honeypot/ports/server_test.go b/honeypot/ports/server_test.go new file mode 100644 index 0000000..5665dc8 --- /dev/null +++ b/honeypot/ports/server_test.go @@ -0,0 +1,45 @@ +package ports_test + +import ( + "net" + "testing" + "time" + + "context" + + "github.uio.no/torjus/apiary/honeypot/ports" +) + +func TestServer(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + store := &ports.MemoryStore{} + server := ports.New(store) + server.IP = "127.0.0.1" + + server.AddTCPPort("25") + + go server.Start(ctx) + + rAddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(server.IP, "25")) + if err != nil { + t.Fatalf("Error resolving remote address: %s", err) + } + conn, err := net.DialTCP("tcp", nil, rAddr) + if err != nil { + t.Fatalf("Dialing server returned error: %s", err) + } + conn.Write([]byte("LOL")) + conn.Close() + time.Sleep(1 * time.Second) + cancel() + + attempts, err := store.List() + if err != nil { + t.Fatalf("Getting attempts from store returned error: %s", err) + } + if len(attempts) != 1 { + t.Fatalf("Wrong amount of attempts in store: %d", len(attempts)) + } +} diff --git a/honeypot/ports/store.go b/honeypot/ports/store.go new file mode 100644 index 0000000..5b2b2fd --- /dev/null +++ b/honeypot/ports/store.go @@ -0,0 +1,26 @@ +package ports + +type ConnectionAttempt struct { + Port string + Network string + From string + Data []byte +} + +type Store interface { + Add(attempt *ConnectionAttempt) error + List() ([]*ConnectionAttempt, error) +} + +type MemoryStore struct { + data []*ConnectionAttempt +} + +func (s *MemoryStore) Add(attempt *ConnectionAttempt) error { + s.data = append(s.data, attempt) + return nil +} + +func (s *MemoryStore) List() ([]*ConnectionAttempt, error) { + return s.data, nil +} diff --git a/version.go b/version.go index a455b01..946226e 100644 --- a/version.go +++ b/version.go @@ -5,7 +5,7 @@ import ( "runtime" ) -var Version = "v0.1.13" +var Version = "v0.1.14" var Build string func FullVersion() string {