Add ports listener feature
This commit is contained in:
parent
94e7faae78
commit
bc9c5dbe0e
17
apiary.toml
17
apiary.toml
@ -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"]
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
71
honeypot/ports/postgresstore.go
Normal file
71
honeypot/ports/postgresstore.go
Normal 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
96
honeypot/ports/server.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
honeypot/ports/server_test.go
Normal file
45
honeypot/ports/server_test.go
Normal 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
26
honeypot/ports/store.go
Normal 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
|
||||||
|
}
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user