frontend with settings and restarting app
This commit is contained in:
+127
-112
@@ -1,138 +1,153 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config contains runtime configuration for the honeypot
|
// Config contains runtime configuration for the honeypot
|
||||||
type Config struct {
|
type Config struct {
|
||||||
LogMode string `json:"log_mode"` // "file" | "stdout" | "sqlite"
|
LogMode string `json:"log_mode"` // "file" | "stdout" | "sqlite"
|
||||||
LogPath string `json:"log_path"`
|
LogPath string `json:"log_path"`
|
||||||
|
|
||||||
Web struct {
|
Web struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Bind string `json:"bind"`
|
Bind string `json:"bind"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
} `json:"web"`
|
} `json:"web"`
|
||||||
|
|
||||||
Services struct {
|
Services struct {
|
||||||
HTTP bool `json:"http"`
|
HTTP bool `json:"http"`
|
||||||
HTTPS bool `json:"https"`
|
HTTPS bool `json:"https"`
|
||||||
SSH bool `json:"ssh"`
|
SSH bool `json:"ssh"`
|
||||||
FTP bool `json:"ftp"`
|
FTP bool `json:"ftp"`
|
||||||
SMTP bool `json:"smtp"`
|
SMTP bool `json:"smtp"`
|
||||||
IMAP bool `json:"imap"`
|
IMAP bool `json:"imap"`
|
||||||
Telnet bool `json:"telnet"`
|
Telnet bool `json:"telnet"`
|
||||||
MySQL bool `json:"mysql"`
|
MySQL bool `json:"mysql"`
|
||||||
PostgreSQL bool `json:"postgresql"`
|
PostgreSQL bool `json:"postgresql"`
|
||||||
MongoDB bool `json:"mongodb"`
|
MongoDB bool `json:"mongodb"`
|
||||||
RDP bool `json:"rdp"`
|
RDP bool `json:"rdp"`
|
||||||
SMB bool `json:"smb"`
|
SMB bool `json:"smb"`
|
||||||
SIP bool `json:"sip"`
|
SIP bool `json:"sip"`
|
||||||
VNC bool `json:"vnc"`
|
VNC bool `json:"vnc"`
|
||||||
Generic []int `json:"generic"`
|
Generic []int `json:"generic"`
|
||||||
} `json:"services"`
|
} `json:"services"`
|
||||||
|
|
||||||
Ports struct {
|
Ports struct {
|
||||||
HTTP int `json:"http"`
|
HTTP int `json:"http"`
|
||||||
HTTPS int `json:"https"`
|
HTTPS int `json:"https"`
|
||||||
SSH int `json:"ssh"`
|
SSH int `json:"ssh"`
|
||||||
FTP int `json:"ftp"`
|
FTP int `json:"ftp"`
|
||||||
SMTP int `json:"smtp"`
|
SMTP int `json:"smtp"`
|
||||||
IMAP int `json:"imap"`
|
IMAP int `json:"imap"`
|
||||||
Telnet int `json:"telnet"`
|
Telnet int `json:"telnet"`
|
||||||
MySQL int `json:"mysql"`
|
MySQL int `json:"mysql"`
|
||||||
PostgreSQL int `json:"postgresql"`
|
PostgreSQL int `json:"postgresql"`
|
||||||
MongoDB int `json:"mongodb"`
|
MongoDB int `json:"mongodb"`
|
||||||
RDP int `json:"rdp"`
|
RDP int `json:"rdp"`
|
||||||
SMB int `json:"smb"`
|
SMB int `json:"smb"`
|
||||||
SIP int `json:"sip"`
|
SIP int `json:"sip"`
|
||||||
VNC int `json:"vnc"`
|
VNC int `json:"vnc"`
|
||||||
} `json:"ports"`
|
} `json:"ports"`
|
||||||
|
|
||||||
// Certificates allows overriding default certificate/key locations.
|
// Certificates allows overriding default certificate/key locations.
|
||||||
Certificates struct {
|
Certificates struct {
|
||||||
// SSHHostKeyPath points to a PEM-encoded RSA private key to use as SSH host key.
|
// SSHHostKeyPath points to a PEM-encoded RSA private key to use as SSH host key.
|
||||||
// If empty, a persistent key will be created in the same directory as LogPath.
|
// If empty, a persistent key will be created in the same directory as LogPath.
|
||||||
SSHHostKeyPath string `json:"ssh_host_key_path"`
|
SSHHostKeyPath string `json:"ssh_host_key_path"`
|
||||||
// TLSCertPath and TLSKeyPath are used by TLS-capable services if provided.
|
// TLSCertPath and TLSKeyPath are used by TLS-capable services if provided.
|
||||||
// If empty, a self-signed certificate will be generated and stored next to LogPath.
|
// If empty, a self-signed certificate will be generated and stored next to LogPath.
|
||||||
TLSCertPath string `json:"tls_cert_path"`
|
TLSCertPath string `json:"tls_cert_path"`
|
||||||
TLSKeyPath string `json:"tls_key_path"`
|
TLSKeyPath string `json:"tls_key_path"`
|
||||||
} `json:"certificates"`
|
} `json:"certificates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LastConfigPath holds the last path used to load/save config.json
|
||||||
|
var LastConfigPath string
|
||||||
|
|
||||||
// EnsureConfig writes a default config file if the given path doesn't exist
|
// EnsureConfig writes a default config file if the given path doesn't exist
|
||||||
func EnsureConfig(path string) error {
|
func EnsureConfig(path string) error {
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
return fmt.Errorf("create config dir: %w", err)
|
return fmt.Errorf("create config dir: %w", err)
|
||||||
}
|
}
|
||||||
def := defaultConfig()
|
def := defaultConfig()
|
||||||
b, _ := json.MarshalIndent(def, "", " ")
|
b, _ := json.MarshalIndent(def, "", " ")
|
||||||
if err := os.WriteFile(path, b, 0644); err != nil {
|
if err := os.WriteFile(path, b, 0644); err != nil {
|
||||||
return fmt.Errorf("write default config: %w", err)
|
return fmt.Errorf("write default config: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig loads JSON config from path
|
// LoadConfig loads JSON config from path
|
||||||
func LoadConfig(path string) (Config, error) {
|
func LoadConfig(path string) (Config, error) {
|
||||||
var cfg Config
|
var cfg Config
|
||||||
b, err := os.ReadFile(path)
|
b, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(b, &cfg); err != nil {
|
if err := json.Unmarshal(b, &cfg); err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
return cfg, nil
|
LastConfigPath = path
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfig writes the given config to the provided path
|
||||||
|
func SaveConfig(path string, cfg Config) error {
|
||||||
|
b, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, b, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultConfig() Config {
|
func defaultConfig() Config {
|
||||||
var c Config
|
var c Config
|
||||||
c.LogMode = "file"
|
c.LogMode = "file"
|
||||||
c.LogPath = "honeypot.log"
|
c.LogPath = "honeypot.log"
|
||||||
c.Web.Enabled = true
|
c.Web.Enabled = true
|
||||||
c.Web.Bind = "127.0.0.1"
|
c.Web.Bind = "127.0.0.1"
|
||||||
c.Web.Port = 6333
|
c.Web.Port = 6333
|
||||||
|
|
||||||
// Enable common services by default
|
// Enable common services by default
|
||||||
c.Services.HTTP = true
|
c.Services.HTTP = true
|
||||||
c.Services.HTTPS = false
|
c.Services.HTTPS = false
|
||||||
c.Services.SSH = true
|
c.Services.SSH = true
|
||||||
c.Services.FTP = true
|
c.Services.FTP = true
|
||||||
c.Services.SMTP = true
|
c.Services.SMTP = true
|
||||||
c.Services.Telnet = true
|
c.Services.Telnet = true
|
||||||
c.Services.MySQL = false
|
c.Services.MySQL = false
|
||||||
c.Services.PostgreSQL = false
|
c.Services.PostgreSQL = false
|
||||||
c.Services.MongoDB = false
|
c.Services.MongoDB = false
|
||||||
c.Services.IMAP = false
|
c.Services.IMAP = false
|
||||||
c.Services.RDP = false
|
c.Services.RDP = false
|
||||||
c.Services.SMB = false
|
c.Services.SMB = false
|
||||||
c.Services.SIP = false
|
c.Services.SIP = false
|
||||||
c.Services.VNC = false
|
c.Services.VNC = false
|
||||||
c.Services.Generic = []int{}
|
c.Services.Generic = []int{}
|
||||||
|
|
||||||
// Standard ports
|
// Standard ports
|
||||||
c.Ports.HTTP = 8080
|
c.Ports.HTTP = 8080
|
||||||
c.Ports.HTTPS = 8443
|
c.Ports.HTTPS = 8443
|
||||||
c.Ports.SSH = 2222
|
c.Ports.SSH = 2222
|
||||||
c.Ports.FTP = 2121
|
c.Ports.FTP = 2121
|
||||||
c.Ports.SMTP = 2525
|
c.Ports.SMTP = 2525
|
||||||
c.Ports.IMAP = 1143
|
c.Ports.IMAP = 1143
|
||||||
c.Ports.Telnet = 2323
|
c.Ports.Telnet = 2323
|
||||||
c.Ports.MySQL = 3306
|
c.Ports.MySQL = 3306
|
||||||
c.Ports.PostgreSQL = 5432
|
c.Ports.PostgreSQL = 5432
|
||||||
c.Ports.MongoDB = 27017
|
c.Ports.MongoDB = 27017
|
||||||
c.Ports.RDP = 3389
|
c.Ports.RDP = 3389
|
||||||
c.Ports.SMB = 4450
|
c.Ports.SIP = 5060
|
||||||
c.Ports.SIP = 5060
|
c.Ports.VNC = 5900
|
||||||
c.Ports.VNC = 5900
|
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-1
@@ -41,6 +41,8 @@ type App struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
listeners []net.Listener
|
listeners []net.Listener
|
||||||
conns map[net.Conn]struct{}
|
conns map[net.Conn]struct{}
|
||||||
|
// restart channel
|
||||||
|
restartCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS honeypot using self-signed or configured certificate
|
// HTTPS honeypot using self-signed or configured certificate
|
||||||
@@ -203,7 +205,7 @@ func NewApp(cfg Config) (*App, error) {
|
|||||||
|
|
||||||
// Root context for the App used for shutdown signalling
|
// Root context for the App used for shutdown signalling
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
a := &App{cfg: cfg, logger: l, threatIntel: ti, ctx: ctx, cancel: cancel, conns: make(map[net.Conn]struct{})}
|
a := &App{cfg: cfg, logger: l, threatIntel: ti, ctx: ctx, cancel: cancel, conns: make(map[net.Conn]struct{}), restartCh: make(chan struct{}, 1)}
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,6 +333,17 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) Restart() {
|
||||||
|
select {
|
||||||
|
case a.restartCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) RestartChan() <-chan struct{} {
|
||||||
|
return a.restartCh
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) Shutdown() {
|
func (a *App) Shutdown() {
|
||||||
a.cancel()
|
a.cancel()
|
||||||
// attempt to close all http servers if running
|
// attempt to close all http servers if running
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{{ define "blacklist_title" }}Blacklist{{ end }}
|
||||||
|
{{ define "blacklist_content" }}
|
||||||
|
<h1 class="text-2xl font-semibold text-white mb-4">Blacklisted IPs</h1>
|
||||||
|
<div class="bg-gray-800 border border-gray-700 rounded-lg">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-700">
|
||||||
|
<thead class="bg-gray-800">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">IP Address</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-gray-900 divide-y divide-gray-800">
|
||||||
|
{{ range .Blacklisted }}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">{{ . }}</td>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">
|
||||||
|
<!-- Actions could be added here (unblacklist) -->
|
||||||
|
<span class="text-gray-500">—</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ else }}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-4 text-sm text-gray-400" colspan="2">No blacklisted IPs.</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{{ define "title" }}Honeypot Dashboard{{ end }}
|
{{ define "index_title" }}Honeypot Dashboard{{ end }}
|
||||||
{{ define "content" }}
|
{{ define "index_content" }}
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<h1 class="text-2xl font-semibold text-white">Honeypot Overview</h1>
|
<h1 class="text-2xl font-semibold text-white">Honeypot Overview</h1>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
@@ -31,4 +31,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "index" }}{{ template "layout.html" . }}{{ end }}
|
|
||||||
|
|||||||
@@ -3,7 +3,15 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{ block "title" . }}Honeypot Dashboard{{ end }}</title>
|
<title>
|
||||||
|
{{ if eq .PageTitle "index_title" }}Honeypot Dashboard
|
||||||
|
{{ else if eq .PageTitle "logs_title" }}Recent Logs
|
||||||
|
{{ else if eq .PageTitle "settings_title" }}Settings
|
||||||
|
{{ else if eq .PageTitle "stats_title" }}Statistics
|
||||||
|
{{ else if eq .PageTitle "blacklist_title" }}Blacklist
|
||||||
|
{{ else if eq .PageTitle "threats_title" }}Top Threats
|
||||||
|
{{ else }}Honeypot Dashboard{{ end }}
|
||||||
|
</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<script>
|
<script>
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
@@ -27,18 +35,31 @@
|
|||||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<div class="flex h-16 items-center justify-between">
|
<div class="flex h-16 items-center justify-between">
|
||||||
<div class="flex items-center space-x-8">
|
<div class="flex items-center space-x-8">
|
||||||
<a href="/" class="text-primary-400 hover:text-primary-300 font-semibold">🍯 Honeypot</a>
|
<a href="/" class="text-primary-400 hover:text-primary-300 font-semibold">Honeypot Dashboard</a>
|
||||||
<a href="/logs" class="text-gray-300 hover:text-white">Recent Logs</a>
|
<a href="/logs" class="text-gray-300 hover:text-white">Recent Logs</a>
|
||||||
<a href="/threats" class="text-gray-300 hover:text-white">Top Threats</a>
|
<a href="/threats" class="text-gray-300 hover:text-white">Top Threats</a>
|
||||||
<a href="/blacklist" class="text-gray-300 hover:text-white">Blacklist</a>
|
<a href="/blacklist" class="text-gray-300 hover:text-white">Blacklist</a>
|
||||||
<a href="/stats" class="text-gray-300 hover:text-white">Statistics</a>
|
<a href="/stats" class="text-gray-300 hover:text-white">Statistics</a>
|
||||||
|
<a href="/settings" class="text-gray-300 hover:text-white">Settings</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-400">{{ .Now }}</div>
|
<div class="text-xs text-gray-400">{{ .Now }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<main class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
|
<main class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
|
||||||
{{ block "content" . }}{{ end }}
|
{{ if eq .PageContent "index_content" }}
|
||||||
|
{{ template "index_content" . }}
|
||||||
|
{{ else if eq .PageContent "logs_content" }}
|
||||||
|
{{ template "logs_content" . }}
|
||||||
|
{{ else if eq .PageContent "settings_content" }}
|
||||||
|
{{ template "settings_content" . }}
|
||||||
|
{{ else if eq .PageContent "stats_content" }}
|
||||||
|
{{ template "stats_content" . }}
|
||||||
|
{{ else if eq .PageContent "blacklist_content" }}
|
||||||
|
{{ template "blacklist_content" . }}
|
||||||
|
{{ else if eq .PageContent "threats_content" }}
|
||||||
|
{{ template "threats_content" . }}
|
||||||
|
{{ end }}
|
||||||
</main>
|
</main>
|
||||||
<footer class="border-t border-gray-800 py-6 text-center text-gray-500 text-sm">Honeypot Dashboard</footer>
|
<footer class="border-t border-gray-800 py-6 text-center text-gray-500 text-sm">Honeypot Dashboard</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{ define "title" }}Recent Logs{{ end }}
|
{{ define "logs_title" }}Recent Logs{{ end }}
|
||||||
{{ define "content" }}
|
{{ define "logs_content" }}
|
||||||
<h1 class="text-2xl font-semibold text-white mb-4">Recent Logs</h1>
|
<h1 class="text-2xl font-semibold text-white mb-4">Recent Logs</h1>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-gray-700">
|
<table class="min-w-full divide-y divide-gray-700">
|
||||||
@@ -26,4 +26,3 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "logs" }}{{ template "layout.html" . }}{{ end }}
|
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
{{ define "settings_title" }}Settings{{ end }}
|
||||||
|
{{ define "settings_content" }}
|
||||||
|
<h1 class="text-2xl font-semibold text-white mb-6">Settings</h1>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<div class="bg-gray-800 border border-gray-700 rounded-lg p-6">
|
||||||
|
<h2 class="text-lg font-semibold text-white mb-4">Services</h2>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{{/* Toggle component */}}
|
||||||
|
{{ $svc := .Cfg.Services }}
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">HTTP</span>
|
||||||
|
<input id="svc-http" type="checkbox" class="h-5 w-5" {{ if $svc.HTTP }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">HTTPS</span>
|
||||||
|
<input id="svc-https" type="checkbox" class="h-5 w-5" {{ if $svc.HTTPS }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">SSH</span>
|
||||||
|
<input id="svc-ssh" type="checkbox" class="h-5 w-5" {{ if $svc.SSH }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">FTP</span>
|
||||||
|
<input id="svc-ftp" type="checkbox" class="h-5 w-5" {{ if $svc.FTP }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">SMTP</span>
|
||||||
|
<input id="svc-smtp" type="checkbox" class="h-5 w-5" {{ if $svc.SMTP }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">IMAP</span>
|
||||||
|
<input id="svc-imap" type="checkbox" class="h-5 w-5" {{ if $svc.IMAP }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">Telnet</span>
|
||||||
|
<input id="svc-telnet" type="checkbox" class="h-5 w-5" {{ if $svc.Telnet }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">MySQL</span>
|
||||||
|
<input id="svc-mysql" type="checkbox" class="h-5 w-5" {{ if $svc.MySQL }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">PostgreSQL</span>
|
||||||
|
<input id="svc-postgresql" type="checkbox" class="h-5 w-5" {{ if $svc.PostgreSQL }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">MongoDB</span>
|
||||||
|
<input id="svc-mongodb" type="checkbox" class="h-5 w-5" {{ if $svc.MongoDB }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">RDP</span>
|
||||||
|
<input id="svc-rdp" type="checkbox" class="h-5 w-5" {{ if $svc.RDP }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">SMB</span>
|
||||||
|
<input id="svc-smb" type="checkbox" class="h-5 w-5" {{ if $svc.SMB }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">SIP</span>
|
||||||
|
<input id="svc-sip" type="checkbox" class="h-5 w-5" {{ if $svc.SIP }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center justify-between bg-gray-900 border border-gray-700 rounded p-3">
|
||||||
|
<span class="text-gray-200">VNC</span>
|
||||||
|
<input id="svc-vnc" type="checkbox" class="h-5 w-5" {{ if $svc.VNC }}checked{{ end }}>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-800 border border-gray-700 rounded-lg p-6">
|
||||||
|
<h2 class="text-lg font-semibold text-white mb-4">Ports</h2>
|
||||||
|
{{ $p := .Cfg.Ports }}
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">HTTP</span>
|
||||||
|
<input id="port-http" type="number" value="{{ $p.HTTP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">HTTPS</span>
|
||||||
|
<input id="port-https" type="number" value="{{ $p.HTTPS }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">SSH</span>
|
||||||
|
<input id="port-ssh" type="number" value="{{ $p.SSH }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">FTP</span>
|
||||||
|
<input id="port-ftp" type="number" value="{{ $p.FTP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">SMTP</span>
|
||||||
|
<input id="port-smtp" type="number" value="{{ $p.SMTP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">IMAP</span>
|
||||||
|
<input id="port-imap" type="number" value="{{ $p.IMAP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">Telnet</span>
|
||||||
|
<input id="port-telnet" type="number" value="{{ $p.Telnet }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">MySQL</span>
|
||||||
|
<input id="port-mysql" type="number" value="{{ $p.MySQL }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">PostgreSQL</span>
|
||||||
|
<input id="port-postgresql" type="number" value="{{ $p.PostgreSQL }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">MongoDB</span>
|
||||||
|
<input id="port-mongodb" type="number" value="{{ $p.MongoDB }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">RDP</span>
|
||||||
|
<input id="port-rdp" type="number" value="{{ $p.RDP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">SMB</span>
|
||||||
|
<input id="port-smb" type="number" value="{{ $p.SMB }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">SIP</span>
|
||||||
|
<input id="port-sip" type="number" value="{{ $p.SIP }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-300">VNC</span>
|
||||||
|
<input id="port-vnc" type="number" value="{{ $p.VNC }}" class="mt-1 w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 flex items-center gap-3">
|
||||||
|
<button id="btn-save" class="px-4 py-2 bg-primary-600 hover:bg-primary-500 rounded text-white">Save Settings</button>
|
||||||
|
<button id="btn-restart" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded text-white">Restart App</button>
|
||||||
|
<span id="save-status" class="text-sm text-gray-400"></span>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
async function saveSettings() {
|
||||||
|
const payload = {
|
||||||
|
services: {
|
||||||
|
http: document.getElementById('svc-http').checked,
|
||||||
|
https: document.getElementById('svc-https').checked,
|
||||||
|
ssh: document.getElementById('svc-ssh').checked,
|
||||||
|
ftp: document.getElementById('svc-ftp').checked,
|
||||||
|
smtp: document.getElementById('svc-smtp').checked,
|
||||||
|
imap: document.getElementById('svc-imap').checked,
|
||||||
|
telnet: document.getElementById('svc-telnet').checked,
|
||||||
|
mysql: document.getElementById('svc-mysql').checked,
|
||||||
|
postgresql: document.getElementById('svc-postgresql').checked,
|
||||||
|
mongodb: document.getElementById('svc-mongodb').checked,
|
||||||
|
rdp: document.getElementById('svc-rdp').checked,
|
||||||
|
smb: document.getElementById('svc-smb').checked,
|
||||||
|
sip: document.getElementById('svc-sip').checked,
|
||||||
|
vnc: document.getElementById('svc-vnc').checked,
|
||||||
|
},
|
||||||
|
ports: {
|
||||||
|
http: parseInt(document.getElementById('port-http').value, 10),
|
||||||
|
https: parseInt(document.getElementById('port-https').value, 10),
|
||||||
|
ssh: parseInt(document.getElementById('port-ssh').value, 10),
|
||||||
|
ftp: parseInt(document.getElementById('port-ftp').value, 10),
|
||||||
|
smtp: parseInt(document.getElementById('port-smtp').value, 10),
|
||||||
|
imap: parseInt(document.getElementById('port-imap').value, 10),
|
||||||
|
telnet: parseInt(document.getElementById('port-telnet').value, 10),
|
||||||
|
mysql: parseInt(document.getElementById('port-mysql').value, 10),
|
||||||
|
postgresql: parseInt(document.getElementById('port-postgresql').value, 10),
|
||||||
|
mongodb: parseInt(document.getElementById('port-mongodb').value, 10),
|
||||||
|
rdp: parseInt(document.getElementById('port-rdp').value, 10),
|
||||||
|
smb: parseInt(document.getElementById('port-smb').value, 10),
|
||||||
|
sip: parseInt(document.getElementById('port-sip').value, 10),
|
||||||
|
vnc: parseInt(document.getElementById('port-vnc').value, 10),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const res = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
||||||
|
const out = await res.json().catch(() => ({}));
|
||||||
|
const el = document.getElementById('save-status');
|
||||||
|
if (res.ok) {
|
||||||
|
el.textContent = 'Saved. You may need to restart to apply port changes.';
|
||||||
|
el.className = 'text-sm text-green-400';
|
||||||
|
} else {
|
||||||
|
el.textContent = out.error || 'Save failed';
|
||||||
|
el.className = 'text-sm text-red-400';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function restartApp() {
|
||||||
|
const res = await fetch('/api/restart', { method: 'POST' });
|
||||||
|
if (res.ok) {
|
||||||
|
document.getElementById('save-status').textContent = 'Restarting...';
|
||||||
|
setTimeout(() => location.reload(), 1200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById('btn-save').addEventListener('click', saveSettings);
|
||||||
|
document.getElementById('btn-restart').addEventListener('click', restartApp);
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
{{ define "stats_title" }}Statistics{{ end }}
|
||||||
|
{{ define "stats_content" }}
|
||||||
|
<h1 class="text-2xl font-semibold text-white mb-4">Statistics</h1>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||||
|
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||||
|
<div class="text-sm text-gray-400">Total IPs</div>
|
||||||
|
<div class="text-3xl font-bold text-primary-400">{{ index .Stats "total_ips" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||||
|
<div class="text-sm text-gray-400">Blacklisted</div>
|
||||||
|
<div class="text-3xl font-bold text-primary-400">{{ index .Stats "blacklisted_ips" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||||
|
<div class="text-sm text-gray-400">Connections</div>
|
||||||
|
<div class="text-3xl font-bold text-primary-400">{{ index .Stats "total_connections" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||||
|
<div class="text-sm text-gray-400">Auth Attempts</div>
|
||||||
|
<div class="text-3xl font-bold text-primary-400">{{ index .Stats "total_auth_attempts" }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
||||||
|
<h2 class="text-xl font-semibold text-white mb-3">Service Activity</h2>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-700">
|
||||||
|
<thead class="bg-gray-800">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Service</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Connections</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-gray-900 divide-y divide-gray-800">
|
||||||
|
{{ range $svc, $cnt := .ServiceStats }}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">{{ $svc }}</td>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">{{ $cnt }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ else }}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-4 text-sm text-gray-400" colspan="2">No data.</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{{ define "threats_title" }}Top Threats{{ end }}
|
||||||
|
{{ define "threats_content" }}
|
||||||
|
<h1 class="text-2xl font-semibold text-white mb-4">Top Threats</h1>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-700">
|
||||||
|
<thead class="bg-gray-800">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">IP</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Threat Score</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Connections</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Auth Attempts</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Services</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Last Seen</th>
|
||||||
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Blacklisted</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-gray-900 divide-y divide-gray-800">
|
||||||
|
{{ range .Threats }}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">{{ .IP }}</td>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300 font-semibold">{{ .ThreatScore }}</td>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">{{ .TotalConnections }}</td>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">{{ .AuthAttempts }}</td>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">{{ len .Services }}</td>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">{{ .LastSeen.Format "2006-01-02 15:04:05" }}</td>
|
||||||
|
<td class="px-4 py-2 text-sm text-gray-300">{{ if .IsBlacklisted }}<span class="text-red-400">Yes</span>{{ else }}No{{ end }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
+182
-3
@@ -25,7 +25,16 @@ func initTemplates() error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Parse layout first, then pages
|
// Parse layout first, then pages
|
||||||
t, err := template.New("layout.html").Funcs(funcMap).ParseFS(templatesFS, "templates/layout.html", "templates/index.html", "templates/logs.html")
|
t, err := template.New("layout.html").Funcs(funcMap).ParseFS(
|
||||||
|
templatesFS,
|
||||||
|
"templates/layout.html",
|
||||||
|
"templates/index.html",
|
||||||
|
"templates/logs.html",
|
||||||
|
"templates/threats.html",
|
||||||
|
"templates/blacklist.html",
|
||||||
|
"templates/stats.html",
|
||||||
|
"templates/settings.html",
|
||||||
|
)
|
||||||
if err != nil { return err }
|
if err != nil { return err }
|
||||||
templates = t
|
templates = t
|
||||||
return nil
|
return nil
|
||||||
@@ -49,13 +58,125 @@ func (a *App) startWeb() {
|
|||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||||
"Stats": stats,
|
"Stats": stats,
|
||||||
|
"PageTitle": "index_title",
|
||||||
|
"PageContent": "index_content",
|
||||||
}
|
}
|
||||||
if templates != nil {
|
if templates != nil {
|
||||||
_ = templates.ExecuteTemplate(w, "index", data)
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Error(w, "templates not loaded", 500)
|
http.Error(w, "templates not loaded", 500)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Settings UI
|
||||||
|
mux.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := map[string]any{
|
||||||
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||||
|
"Cfg": a.cfg,
|
||||||
|
"PageTitle": "settings_title",
|
||||||
|
"PageContent": "settings_content",
|
||||||
|
}
|
||||||
|
if templates != nil {
|
||||||
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, "templates not loaded", 500)
|
||||||
|
})
|
||||||
|
// API to read/update settings (services + ports)
|
||||||
|
mux.HandleFunc("/api/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
_ = json.NewEncoder(w).Encode(a.cfg)
|
||||||
|
return
|
||||||
|
case http.MethodPost:
|
||||||
|
var in struct {
|
||||||
|
Services struct {
|
||||||
|
HTTP bool `json:"http"`
|
||||||
|
HTTPS bool `json:"https"`
|
||||||
|
SSH bool `json:"ssh"`
|
||||||
|
FTP bool `json:"ftp"`
|
||||||
|
SMTP bool `json:"smtp"`
|
||||||
|
IMAP bool `json:"imap"`
|
||||||
|
Telnet bool `json:"telnet"`
|
||||||
|
MySQL bool `json:"mysql"`
|
||||||
|
PostgreSQL bool `json:"postgresql"`
|
||||||
|
MongoDB bool `json:"mongodb"`
|
||||||
|
RDP bool `json:"rdp"`
|
||||||
|
SMB bool `json:"smb"`
|
||||||
|
SIP bool `json:"sip"`
|
||||||
|
VNC bool `json:"vnc"`
|
||||||
|
} `json:"services"`
|
||||||
|
Ports struct {
|
||||||
|
HTTP int `json:"http"`
|
||||||
|
HTTPS int `json:"https"`
|
||||||
|
SSH int `json:"ssh"`
|
||||||
|
FTP int `json:"ftp"`
|
||||||
|
SMTP int `json:"smtp"`
|
||||||
|
IMAP int `json:"imap"`
|
||||||
|
Telnet int `json:"telnet"`
|
||||||
|
MySQL int `json:"mysql"`
|
||||||
|
PostgreSQL int `json:"postgresql"`
|
||||||
|
MongoDB int `json:"mongodb"`
|
||||||
|
RDP int `json:"rdp"`
|
||||||
|
SMB int `json:"smb"`
|
||||||
|
SIP int `json:"sip"`
|
||||||
|
VNC int `json:"vnc"`
|
||||||
|
} `json:"ports"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]string{"error":"bad json"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Update in-memory
|
||||||
|
a.cfg.Services.HTTP = in.Services.HTTP
|
||||||
|
a.cfg.Services.HTTPS = in.Services.HTTPS
|
||||||
|
a.cfg.Services.SSH = in.Services.SSH
|
||||||
|
a.cfg.Services.FTP = in.Services.FTP
|
||||||
|
a.cfg.Services.SMTP = in.Services.SMTP
|
||||||
|
a.cfg.Services.IMAP = in.Services.IMAP
|
||||||
|
a.cfg.Services.Telnet = in.Services.Telnet
|
||||||
|
a.cfg.Services.MySQL = in.Services.MySQL
|
||||||
|
a.cfg.Services.PostgreSQL = in.Services.PostgreSQL
|
||||||
|
a.cfg.Services.MongoDB = in.Services.MongoDB
|
||||||
|
a.cfg.Services.RDP = in.Services.RDP
|
||||||
|
a.cfg.Services.SMB = in.Services.SMB
|
||||||
|
a.cfg.Services.SIP = in.Services.SIP
|
||||||
|
a.cfg.Services.VNC = in.Services.VNC
|
||||||
|
|
||||||
|
a.cfg.Ports.HTTP = in.Ports.HTTP
|
||||||
|
a.cfg.Ports.HTTPS = in.Ports.HTTPS
|
||||||
|
a.cfg.Ports.SSH = in.Ports.SSH
|
||||||
|
a.cfg.Ports.FTP = in.Ports.FTP
|
||||||
|
a.cfg.Ports.SMTP = in.Ports.SMTP
|
||||||
|
a.cfg.Ports.IMAP = in.Ports.IMAP
|
||||||
|
a.cfg.Ports.Telnet = in.Ports.Telnet
|
||||||
|
a.cfg.Ports.MySQL = in.Ports.MySQL
|
||||||
|
a.cfg.Ports.PostgreSQL = in.Ports.PostgreSQL
|
||||||
|
a.cfg.Ports.MongoDB = in.Ports.MongoDB
|
||||||
|
a.cfg.Ports.RDP = in.Ports.RDP
|
||||||
|
a.cfg.Ports.SMB = in.Ports.SMB
|
||||||
|
a.cfg.Ports.SIP = in.Ports.SIP
|
||||||
|
a.cfg.Ports.VNC = in.Ports.VNC
|
||||||
|
|
||||||
|
// Persist to ./config.json
|
||||||
|
if b, err := json.MarshalIndent(a.cfg, "", " "); err == nil {
|
||||||
|
_ = os.WriteFile("config.json", b, 0644)
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]string{"status":"ok"})
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Restart endpoint: triggers app restart
|
||||||
|
mux.HandleFunc("/api/restart", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed); return }
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]string{"status":"restarting"})
|
||||||
|
go func(){ time.Sleep(700*time.Millisecond); a.Restart() }()
|
||||||
|
})
|
||||||
mux.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// display last 200 logs
|
// display last 200 logs
|
||||||
var rows []Record
|
var rows []Record
|
||||||
@@ -102,9 +223,67 @@ func (a *App) startWeb() {
|
|||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||||
"Rows": rows,
|
"Rows": rows,
|
||||||
|
"PageTitle": "logs_title",
|
||||||
|
"PageContent": "logs_content",
|
||||||
}
|
}
|
||||||
if templates != nil {
|
if templates != nil {
|
||||||
_ = templates.ExecuteTemplate(w, "logs", data)
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, "templates not loaded", 500)
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/threats", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var threats []*IPThreatInfo
|
||||||
|
if a.threatIntel != nil {
|
||||||
|
threats = a.threatIntel.GetTopThreats(50)
|
||||||
|
}
|
||||||
|
data := map[string]any{
|
||||||
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||||
|
"Threats": threats,
|
||||||
|
"PageTitle": "threats_title",
|
||||||
|
"PageContent": "threats_content",
|
||||||
|
}
|
||||||
|
if templates != nil {
|
||||||
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, "templates not loaded", 500)
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/blacklist", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var bl []string
|
||||||
|
if a.threatIntel != nil {
|
||||||
|
bl = a.threatIntel.GetBlacklistedIPs()
|
||||||
|
}
|
||||||
|
data := map[string]any{
|
||||||
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||||
|
"Blacklisted": bl,
|
||||||
|
"PageTitle": "blacklist_title",
|
||||||
|
"PageContent": "blacklist_content",
|
||||||
|
}
|
||||||
|
if templates != nil {
|
||||||
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, "templates not loaded", 500)
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
stats := map[string]any{}
|
||||||
|
var svc map[string]int
|
||||||
|
if a.threatIntel != nil {
|
||||||
|
stats = a.threatIntel.GetStats()
|
||||||
|
if v, ok := stats["service_stats"].(map[string]int); ok {
|
||||||
|
svc = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data := map[string]any{
|
||||||
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
||||||
|
"Stats": stats,
|
||||||
|
"ServiceStats": svc,
|
||||||
|
"PageTitle": "stats_title",
|
||||||
|
"PageContent": "stats_content",
|
||||||
|
}
|
||||||
|
if templates != nil {
|
||||||
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Error(w, "templates not loaded", 500)
|
http.Error(w, "templates not loaded", 500)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"honeydany/app"
|
"honeydany/app"
|
||||||
)
|
)
|
||||||
@@ -16,36 +17,55 @@ func main() {
|
|||||||
cfgPath := flag.String("config", "config.json", "path to config.json")
|
cfgPath := flag.String("config", "config.json", "path to config.json")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// ensure config exists (auto-generate default if missing)
|
for {
|
||||||
if err := app.EnsureConfig(*cfgPath); err != nil {
|
// ensure config exists (auto-generate default if missing)
|
||||||
log.Fatalf("ensure config: %v", err)
|
if err := app.EnsureConfig(*cfgPath); err != nil {
|
||||||
}
|
log.Fatalf("ensure config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
cfg, err := app.LoadConfig(*cfgPath)
|
cfg, err := app.LoadConfig(*cfgPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load config: %v", err)
|
log.Fatalf("load config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a, err := app.NewApp(cfg)
|
a, err := app.NewApp(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("create app: %v", err)
|
log.Fatalf("create app: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
|
||||||
|
// handle signals and restart
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
var shouldRestart bool
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-sigCh:
|
||||||
|
fmt.Println("signal received, shutting down")
|
||||||
|
cancel()
|
||||||
|
a.Shutdown()
|
||||||
|
case <-a.RestartChan():
|
||||||
|
fmt.Println("restart requested, reloading configuration")
|
||||||
|
shouldRestart = true
|
||||||
|
cancel()
|
||||||
|
a.Shutdown()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := a.Run(ctx); err != nil {
|
||||||
|
log.Fatalf("app run: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// handle signals
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
go func() {
|
|
||||||
<-sigCh
|
|
||||||
fmt.Println("signal received, shutting down")
|
|
||||||
cancel()
|
cancel()
|
||||||
a.Shutdown()
|
|
||||||
}()
|
if !shouldRestart {
|
||||||
|
break
|
||||||
if err := a.Run(ctx); err != nil {
|
}
|
||||||
log.Fatalf("app run: %v", err)
|
|
||||||
|
fmt.Println("restarting honeypot...")
|
||||||
|
time.Sleep(1 * time.Second) // Brief pause before restart
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("honeypot stopped")
|
fmt.Println("honeypot stopped")
|
||||||
|
|||||||
Reference in New Issue
Block a user