Add ports listener feature

This commit is contained in:
Torjus Håkestad 2021-10-21 10:33:28 +02:00
parent 94e7faae78
commit bc9c5dbe0e
8 changed files with 286 additions and 1 deletions

View File

@ -59,3 +59,20 @@ CacheDir = "/var/apiary/certs"
# Redirect HTTP to HTTPS # Redirect HTTP to HTTPS
# Default: true # Default: true
RedirectHTTP = 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"]

View File

@ -12,6 +12,7 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.uio.no/torjus/apiary" "github.uio.no/torjus/apiary"
"github.uio.no/torjus/apiary/config" "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"
"github.uio.no/torjus/apiary/honeypot/ssh/store" "github.uio.no/torjus/apiary/honeypot/ssh/store"
"github.uio.no/torjus/apiary/web" "github.uio.no/torjus/apiary/web"
@ -113,6 +114,25 @@ func ActionServe(c *cli.Context) error {
rootCtx, rootCancel := context.WithCancel(c.Context) rootCtx, rootCancel := context.WithCancel(c.Context)
serversCtx, serversCancel := context.WithCancel(rootCtx) 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 // Handle interrupt
go func() { go func() {
<-interruptChan <-interruptChan
@ -171,6 +191,7 @@ type loggerCollection struct {
honeypotLogger *zap.SugaredLogger honeypotLogger *zap.SugaredLogger
webAccessLogger *zap.SugaredLogger webAccessLogger *zap.SugaredLogger
webServerLogger *zap.SugaredLogger webServerLogger *zap.SugaredLogger
portsLogger *zap.SugaredLogger
} }
func setupLoggers(cfg config.Config) *loggerCollection { func setupLoggers(cfg config.Config) *loggerCollection {
@ -198,6 +219,7 @@ func setupLoggers(cfg config.Config) *loggerCollection {
honeypotLogger: rootLogger.Named("HON").Sugar(), honeypotLogger: rootLogger.Named("HON").Sugar(),
webAccessLogger: rootLogger.Named("ACC").Sugar(), webAccessLogger: rootLogger.Named("ACC").Sugar(),
webServerLogger: rootLogger.Named("WEB").Sugar(), webServerLogger: rootLogger.Named("WEB").Sugar(),
portsLogger: rootLogger.Named("PRT").Sugar(),
} }
} }

View File

@ -13,6 +13,7 @@ type Config struct {
Store StoreConfig `toml:"Store"` Store StoreConfig `toml:"Store"`
Honeypot HoneypotConfig `toml:"Honeypot"` Honeypot HoneypotConfig `toml:"Honeypot"`
Frontend FrontendConfig `toml:"Frontend"` Frontend FrontendConfig `toml:"Frontend"`
Ports PortsConfig `toml:"Ports"`
} }
type StoreConfig struct { type StoreConfig struct {
Type string `toml:"Type"` Type string `toml:"Type"`
@ -46,6 +47,13 @@ type FrontendAutocertConfig struct {
RedirectHTTP bool `toml:"RedirectHTTP"` 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) { func FromReader(r io.Reader) (Config, error) {
var c Config var c Config

View File

@ -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
}

96
honeypot/ports/server.go Normal file
View File

@ -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)
}
}
}

View File

@ -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))
}
}

26
honeypot/ports/store.go Normal file
View File

@ -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
}

View File

@ -5,7 +5,7 @@ import (
"runtime" "runtime"
) )
var Version = "v0.1.13" var Version = "v0.1.14"
var Build string var Build string
func FullVersion() string { func FullVersion() string {