update services to separate files
This commit is contained in:
+191
-22
@@ -4,18 +4,25 @@ import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"encoding/pem"
|
||||
"golang.org/x/crypto/ssh"
|
||||
svcs "honeydany/app/services"
|
||||
)
|
||||
|
||||
// App holds runtime pieces
|
||||
@@ -35,6 +42,52 @@ type App struct {
|
||||
conns map[net.Conn]struct{}
|
||||
}
|
||||
|
||||
// HTTPS honeypot using self-signed or configured certificate
|
||||
func (a *App) startHTTPS(port int) {
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
var bodySnippet string
|
||||
if r.Body != nil {
|
||||
b, _ := io.ReadAll(io.LimitReader(r.Body, 1024*64))
|
||||
bodySnippet = string(b)
|
||||
}
|
||||
details := map[string]string{"method": r.Method, "url": r.URL.String(), "proto": r.Proto}
|
||||
if ua := r.Header.Get("User-Agent"); ua != "" { details["user_agent"] = ua }
|
||||
if sni := r.TLS; sni != nil && sni.ServerName != "" { details["sni_server_name"] = sni.ServerName }
|
||||
rec := Record{Timestamp: time.Now().UTC(), RemoteAddr: remoteIP(r.RemoteAddr), RemotePort: remotePort(r.RemoteAddr), Service: "https", Details: details, RawPayload: bodySnippet}
|
||||
a.logEvent(rec)
|
||||
|
||||
w.Header().Set("Server", "nginx/1.18.0 (Ubuntu)")
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(200)
|
||||
_, _ = w.Write([]byte("Welcome (TLS)\n"))
|
||||
})
|
||||
|
||||
// Load or create certificate
|
||||
cert, _, _, err := a.GetOrCreateTLSCertificate()
|
||||
if err != nil {
|
||||
log.Printf("HTTPS certificate error: %v", err)
|
||||
return
|
||||
}
|
||||
tlsCfg := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
|
||||
srv := &http.Server{Addr: addr, Handler: mux, TLSConfig: tlsCfg}
|
||||
a.addHTTPServer(srv)
|
||||
log.Printf("HTTPS listening on %s", addr)
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Printf("https listen failed on %s: %v", addr, err)
|
||||
return
|
||||
}
|
||||
// Wrap with TLS and serve
|
||||
tlsLn := tls.NewListener(ln, tlsCfg)
|
||||
if err := srv.Serve(tlsLn); err != nil && err != http.ErrServerClosed {
|
||||
log.Printf("https server error: %v", err)
|
||||
}
|
||||
log.Printf("https stopped")
|
||||
}
|
||||
|
||||
// addHTTPServer registers an HTTP server for graceful shutdown
|
||||
func (a *App) addHTTPServer(s *http.Server) {
|
||||
a.mu.Lock()
|
||||
@@ -158,123 +211,131 @@ func (a *App) Run(ctx context.Context) error {
|
||||
a.startHTTP(a.cfg.Ports.HTTP)
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.HTTPS {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startHTTPS(a.cfg.Ports.HTTPS)
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SSH {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("ssh", a.cfg.Ports.SSH, a.sshHandler)
|
||||
handler := svcs.NewSSHHandler(a.svcLogger(), a.getSSHSigner)
|
||||
a.startTCPService("ssh", a.cfg.Ports.SSH, handler)
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.FTP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("ftp", a.cfg.Ports.FTP, a.ftpHandler)
|
||||
a.startTCPService("ftp", a.cfg.Ports.FTP, svcs.NewFTPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SMTP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("smtp", a.cfg.Ports.SMTP, a.smtpHandler)
|
||||
a.startTCPService("smtp", a.cfg.Ports.SMTP, svcs.NewSMTPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.POP3 {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("pop3", a.cfg.Ports.POP3, a.pop3Handler)
|
||||
a.startTCPService("pop3", a.cfg.Ports.POP3, svcs.NewPOP3Handler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.IMAP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("imap", a.cfg.Ports.IMAP, a.imapHandler)
|
||||
a.startTCPService("imap", a.cfg.Ports.IMAP, svcs.NewIMAPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.Telnet {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("telnet", a.cfg.Ports.Telnet, a.telnetHandler)
|
||||
a.startTCPService("telnet", a.cfg.Ports.Telnet, svcs.NewTelnetHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.MySQL {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("mysql", a.cfg.Ports.MySQL, a.mysqlHandler)
|
||||
a.startTCPService("mysql", a.cfg.Ports.MySQL, svcs.NewMySQLHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.PostgreSQL {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("postgresql", a.cfg.Ports.PostgreSQL, a.postgresqlHandler)
|
||||
a.startTCPService("postgresql", a.cfg.Ports.PostgreSQL, svcs.NewPostgreSQLHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.Redis {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("redis", a.cfg.Ports.Redis, a.redisHandler)
|
||||
a.startTCPService("redis", a.cfg.Ports.Redis, svcs.NewRedisHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.MongoDB {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("mongodb", a.cfg.Ports.MongoDB, a.mongodbHandler)
|
||||
a.startTCPService("mongodb", a.cfg.Ports.MongoDB, svcs.NewMongoDBHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.RDP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("rdp", a.cfg.Ports.RDP, a.rdpHandler)
|
||||
a.startTCPService("rdp", a.cfg.Ports.RDP, svcs.NewRDPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SMB {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("smb", a.cfg.Ports.SMB, a.genericBannerHandler("SMB-Server-1.0"))
|
||||
a.startTCPService("smb", a.cfg.Ports.SMB, svcs.NewGenericBannerHandler("SMB-Server-1.0", a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.VNC {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("vnc", a.cfg.Ports.VNC, a.genericBannerHandler("RFB 003.008"))
|
||||
a.startTCPService("vnc", a.cfg.Ports.VNC, svcs.NewGenericBannerHandler("RFB 003.008", a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SIP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("sip", a.cfg.Ports.SIP, a.sipHandler)
|
||||
a.startTCPService("sip", a.cfg.Ports.SIP, svcs.NewSIPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.DNS {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("dns", a.cfg.Ports.DNS, a.dnsHandler)
|
||||
a.startTCPService("dns", a.cfg.Ports.DNS, svcs.NewDNSHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SNMP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("snmp", a.cfg.Ports.SNMP, a.snmpHandler)
|
||||
a.startTCPService("snmp", a.cfg.Ports.SNMP, svcs.NewSNMPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.LDAP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("ldap", a.cfg.Ports.LDAP, a.ldapHandler)
|
||||
a.startTCPService("ldap", a.cfg.Ports.LDAP, svcs.NewLDAPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
for _, p := range a.cfg.Services.Generic {
|
||||
@@ -282,7 +343,7 @@ func (a *App) Run(ctx context.Context) error {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService(fmt.Sprintf("generic-%d", port), port, a.genericEchoHandler)
|
||||
a.startTCPService(fmt.Sprintf("generic-%d", port), port, svcs.NewGenericEchoHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -380,6 +441,20 @@ func (a *App) logEvent(r Record) {
|
||||
}
|
||||
}
|
||||
|
||||
// svcLogger adapts services.Record to the app Record and logs it
|
||||
func (a *App) svcLogger() func(svcs.Record) {
|
||||
return func(sr svcs.Record) {
|
||||
a.logEvent(Record{
|
||||
Timestamp: sr.Timestamp,
|
||||
RemoteAddr: sr.RemoteAddr,
|
||||
RemotePort: sr.RemotePort,
|
||||
Service: sr.Service,
|
||||
Details: sr.Details,
|
||||
RawPayload: sr.RawPayload,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP honeypot
|
||||
func (a *App) startHTTP(port int) {
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
@@ -549,11 +624,15 @@ func (a *App) getSSHSigner() (ssh.Signer, error) {
|
||||
if a.sshSigner != nil {
|
||||
return a.sshSigner, nil
|
||||
}
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Determine host key path: config override or alongside LogPath
|
||||
var hostKeyPath string
|
||||
if a.cfg.Certificates.SSHHostKeyPath != "" {
|
||||
hostKeyPath = a.cfg.Certificates.SSHHostKeyPath
|
||||
} else {
|
||||
baseDir := a.dataDir()
|
||||
hostKeyPath = filepath.Join(baseDir, "ssh_host_key.pem")
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(key)
|
||||
signer, err := a.loadOrCreateSSHHostKey(hostKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -561,6 +640,96 @@ func (a *App) getSSHSigner() (ssh.Signer, error) {
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// dataDir returns the directory where persistent data should live (same dir as LogPath)
|
||||
func (a *App) dataDir() string {
|
||||
if a.cfg.LogPath == "" {
|
||||
return "."
|
||||
}
|
||||
return filepath.Dir(a.cfg.LogPath)
|
||||
}
|
||||
|
||||
// loadOrCreateSSHHostKey loads an RSA private key from PEM or generates and saves one
|
||||
func (a *App) loadOrCreateSSHHostKey(path string) (ssh.Signer, error) {
|
||||
if b, err := os.ReadFile(path); err == nil {
|
||||
// Try parse PEM private key
|
||||
block, _ := pem.Decode(b)
|
||||
if block != nil && strings.Contains(block.Type, "PRIVATE KEY") {
|
||||
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
|
||||
return ssh.NewSignerFromKey(key)
|
||||
}
|
||||
if k2, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
|
||||
return ssh.NewSignerFromKey(k2)
|
||||
}
|
||||
}
|
||||
// If parse failed, fall through to generate new and overwrite
|
||||
}
|
||||
// Generate new RSA key and persist to PEM
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pemBlock := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
||||
if err := os.WriteFile(path, pem.EncodeToMemory(pemBlock), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ssh.NewSignerFromKey(key)
|
||||
}
|
||||
|
||||
// GetOrCreateTLSCertificate loads a TLS cert/key if provided or generates a self-signed pair
|
||||
// in the data directory (next to LogPath) and returns the tls.Certificate and the file paths used.
|
||||
func (a *App) GetOrCreateTLSCertificate() (tls.Certificate, string, string, error) {
|
||||
// If custom cert/key provided, try load them
|
||||
if a.cfg.Certificates.TLSCertPath != "" && a.cfg.Certificates.TLSKeyPath != "" {
|
||||
cert, err := tls.LoadX509KeyPair(a.cfg.Certificates.TLSCertPath, a.cfg.Certificates.TLSKeyPath)
|
||||
return cert, a.cfg.Certificates.TLSCertPath, a.cfg.Certificates.TLSKeyPath, err
|
||||
}
|
||||
// Else generate or reuse self-signed in data dir
|
||||
dir := a.dataDir()
|
||||
certPath := filepath.Join(dir, "tls_cert.pem")
|
||||
keyPath := filepath.Join(dir, "tls_key.pem")
|
||||
if _, err := os.Stat(certPath); err == nil {
|
||||
if _, err2 := os.Stat(keyPath); err2 == nil {
|
||||
cert, err3 := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
return cert, certPath, keyPath, err3
|
||||
}
|
||||
}
|
||||
// create self-signed cert
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
serial, _ := rand.Int(rand.Reader, big.NewInt(1<<62))
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{CommonName: "honeypot.local"},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().AddDate(5, 0, 0),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
certOut := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
|
||||
keyOut := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
if err := os.WriteFile(certPath, certOut, 0600); err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
if err := os.WriteFile(keyPath, keyOut, 0600); err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
cert, err := tls.X509KeyPair(certOut, keyOut)
|
||||
return cert, certPath, keyPath, err
|
||||
}
|
||||
|
||||
// logSSHEvent is a helper to log SSH-specific events with consistent metadata
|
||||
func (a *App) logSSHEvent(sessionID, remote, eventType string, details map[string]string) {
|
||||
if details == nil {
|
||||
|
||||
Reference in New Issue
Block a user