478 lines
18 KiB
Go
478 lines
18 KiB
Go
package app
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"honeydany/app/dashboard"
|
|
)
|
|
|
|
//go:embed templates/*.html
|
|
var templatesFS embed.FS
|
|
|
|
var templates *template.Template
|
|
|
|
func initTemplates() error {
|
|
funcMap := template.FuncMap{
|
|
"toJSON": func(v any) string {
|
|
b, _ := json.Marshal(v)
|
|
return string(b)
|
|
},
|
|
}
|
|
// Parse layout first, then pages
|
|
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",
|
|
"templates/threat_reports.html",
|
|
"templates/threat_rules.html",
|
|
"templates/webtemplates.html",
|
|
"templates/users.html",
|
|
)
|
|
if err != nil { return err }
|
|
templates = t
|
|
return nil
|
|
}
|
|
|
|
func (a *App) startWeb() {
|
|
bind := a.cfg.Web.Bind
|
|
port := a.cfg.Web.Port
|
|
addr := fmt.Sprintf("%s:%d", bind, port)
|
|
mux := http.NewServeMux()
|
|
if templates == nil {
|
|
if err := initTemplates(); err != nil {
|
|
log.Printf("template init error: %v", err)
|
|
}
|
|
}
|
|
|
|
// Register authentication and threat analysis routes if threat manager is available
|
|
if a.threatManager != nil {
|
|
var _ *dashboard.ThreatAPI = a.threatManager.GetAPI() // Ensure dashboard import is used
|
|
|
|
// Get security manager
|
|
securityManager := a.threatManager.GetSecurityManager()
|
|
|
|
// Register user management routes (includes login/logout)
|
|
a.threatManager.GetUserAPI().RegisterUserRoutes(mux, securityManager)
|
|
|
|
// Register threat analysis API routes (they will handle their own authentication)
|
|
a.threatManager.GetAPI().RegisterRoutes(mux)
|
|
|
|
// Register blocklist export routes (public endpoints for threat intelligence sharing)
|
|
a.threatManager.GetBlocklistExporter().RegisterExportRoutes(mux, securityManager)
|
|
|
|
// Register web template management routes (admin only)
|
|
a.threatManager.GetWebTemplateAPI().RegisterRoutes(mux, securityManager)
|
|
|
|
// Register web services management routes (admin only)
|
|
a.threatManager.GetWebServicesAPI().RegisterRoutes(mux, securityManager)
|
|
}
|
|
|
|
// Secure dashboard routes with authentication
|
|
var authMiddleware func(http.HandlerFunc) http.HandlerFunc
|
|
if a.threatManager != nil {
|
|
authMiddleware = a.threatManager.GetSecurityManager().AuthMiddleware
|
|
} else {
|
|
// Fallback if threat manager is not available
|
|
authMiddleware = func(next http.HandlerFunc) http.HandlerFunc {
|
|
return next // No authentication
|
|
}
|
|
}
|
|
|
|
mux.HandleFunc("/", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
|
stats := map[string]any{}
|
|
if a.threatIntel != nil {
|
|
stats = a.threatIntel.GetStats()
|
|
}
|
|
data := map[string]any{
|
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
|
"Stats": stats,
|
|
"PageTitle": "index_title",
|
|
"PageContent": "index_content",
|
|
}
|
|
// Add CSRF token if security manager is available
|
|
if a.threatManager != nil {
|
|
a.threatManager.GetSecurityManager().AddCSRFToken(w, data)
|
|
}
|
|
if templates != nil {
|
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
|
return
|
|
}
|
|
http.Error(w, "templates not loaded", 500)
|
|
}))
|
|
|
|
// Settings UI
|
|
mux.HandleFunc("/settings", authMiddleware(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",
|
|
}
|
|
// Add CSRF token if security manager is available
|
|
if a.threatManager != nil {
|
|
a.threatManager.GetSecurityManager().AddCSRFToken(w, data)
|
|
}
|
|
if templates != nil {
|
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
|
return
|
|
}
|
|
http.Error(w, "templates not loaded", 500)
|
|
}))
|
|
// API to read/update settings (services + ports)
|
|
var apiAuthMiddleware func(http.HandlerFunc) http.HandlerFunc
|
|
var csrfMiddleware func(http.HandlerFunc) http.HandlerFunc
|
|
if a.threatManager != nil {
|
|
apiAuthMiddleware = a.threatManager.GetSecurityManager().APIAuthMiddleware
|
|
csrfMiddleware = a.threatManager.GetSecurityManager().CSRFMiddleware
|
|
} else {
|
|
// Fallback if threat manager is not available
|
|
apiAuthMiddleware = func(next http.HandlerFunc) http.HandlerFunc { return next }
|
|
csrfMiddleware = func(next http.HandlerFunc) http.HandlerFunc { return next }
|
|
}
|
|
|
|
mux.HandleFunc("/api/settings", apiAuthMiddleware(csrfMiddleware(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"`
|
|
Web struct {
|
|
HTTPTemplateName string `json:"http_template_name"`
|
|
HTTPSTemplateName string `json:"https_template_name"`
|
|
} `json:"web"`
|
|
}
|
|
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
|
|
|
|
// Update web template settings
|
|
a.cfg.Web.HTTPTemplateName = in.Web.HTTPTemplateName
|
|
a.cfg.Web.HTTPSTemplateName = in.Web.HTTPSTemplateName
|
|
|
|
// Persist to ./config.json
|
|
if b, err := json.MarshalIndent(a.cfg, "", " "); err == nil {
|
|
_ = os.WriteFile("config.json", b, 0644)
|
|
}
|
|
|
|
// Generate new CSRF token for subsequent requests
|
|
response := map[string]string{"status": "ok"}
|
|
if a.threatManager != nil {
|
|
if newToken, err := a.threatManager.GetSecurityManager().GenerateCSRFToken(); err == nil {
|
|
response["csrf_token"] = newToken
|
|
w.Header().Set("X-CSRF-Token", newToken)
|
|
}
|
|
}
|
|
_ = json.NewEncoder(w).Encode(response)
|
|
return
|
|
default:
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
})))
|
|
// Restart endpoint: triggers app restart
|
|
mux.HandleFunc("/api/restart", apiAuthMiddleware(csrfMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed); return }
|
|
|
|
// Generate new CSRF token for subsequent requests
|
|
response := map[string]string{"status": "restarting"}
|
|
if a.threatManager != nil {
|
|
if newToken, err := a.threatManager.GetSecurityManager().GenerateCSRFToken(); err == nil {
|
|
response["csrf_token"] = newToken
|
|
w.Header().Set("X-CSRF-Token", newToken)
|
|
}
|
|
}
|
|
_ = json.NewEncoder(w).Encode(response)
|
|
go func(){ time.Sleep(700*time.Millisecond); a.Restart() }()
|
|
})))
|
|
mux.HandleFunc("/logs", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
|
// display last 200 logs
|
|
var rows []Record
|
|
if a.logger != nil && a.logger.mode == "sqlite" && a.logger.db != nil {
|
|
// query sqlite
|
|
q := `SELECT timestamp, remote_addr, remote_port, service, details, raw_payload FROM logs ORDER BY id DESC LIMIT 200`
|
|
rs, err := a.logger.db.Query(q)
|
|
if err != nil {
|
|
http.Error(w, "db query failed", 500)
|
|
return
|
|
}
|
|
defer rs.Close()
|
|
for rs.Next() {
|
|
var ts, ra, rp, svc, detailsS, raw string
|
|
if err := rs.Scan(&ts, &ra, &rp, &svc, &detailsS, &raw); err != nil {
|
|
continue
|
|
}
|
|
var det map[string]string
|
|
if err := json.Unmarshal([]byte(detailsS), &det); err != nil {
|
|
continue
|
|
}
|
|
rows = append(rows, Record{Timestamp: parseTime(ts), RemoteAddr: ra, RemotePort: rp, Service: svc, Details: det, RawPayload: raw})
|
|
}
|
|
} else {
|
|
// try to read file based JSON-lines
|
|
path := a.cfg.LogPath
|
|
b, err := os.ReadFile(path)
|
|
if err == nil {
|
|
lines := strings.Split(string(b), "\n")
|
|
for i := len(lines) - 1; i >= 0 && len(rows) < 200; i-- {
|
|
line := strings.TrimSpace(lines[i])
|
|
if line == "" {
|
|
continue
|
|
}
|
|
var rec Record
|
|
if err := json.Unmarshal([]byte(line), &rec); err != nil {
|
|
continue
|
|
}
|
|
rows = append(rows, rec)
|
|
}
|
|
}
|
|
}
|
|
|
|
data := map[string]any{
|
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
|
"Rows": rows,
|
|
"PageTitle": "logs_title",
|
|
"PageContent": "logs_content",
|
|
}
|
|
if templates != nil {
|
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
|
return
|
|
}
|
|
http.Error(w, "templates not loaded", 500)
|
|
}))
|
|
mux.HandleFunc("/threats", authMiddleware(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", authMiddleware(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", authMiddleware(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
|
|
}
|
|
http.Error(w, "templates not loaded", 500)
|
|
}))
|
|
|
|
// Threat Reports page
|
|
mux.HandleFunc("/threat-reports", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
|
data := map[string]any{
|
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
|
"PageTitle": "threat_reports_title",
|
|
"PageContent": "threat_reports_content",
|
|
}
|
|
if templates != nil {
|
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
|
return
|
|
}
|
|
http.Error(w, "templates not loaded", 500)
|
|
}))
|
|
|
|
// Threat Rules page
|
|
mux.HandleFunc("/threat-rules", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
|
data := map[string]any{
|
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
|
"PageTitle": "threat_rules_title",
|
|
"PageContent": "threat_rules_content",
|
|
}
|
|
// Add CSRF token if security manager is available
|
|
if a.threatManager != nil {
|
|
a.threatManager.GetSecurityManager().AddCSRFToken(w, data)
|
|
}
|
|
if templates != nil {
|
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
|
return
|
|
}
|
|
http.Error(w, "templates not loaded", 500)
|
|
}))
|
|
|
|
// Role middleware setup for admin pages
|
|
var roleMiddleware func(string) func(http.HandlerFunc) http.HandlerFunc
|
|
if a.threatManager != nil {
|
|
roleMiddleware = a.threatManager.GetSecurityManager().RoleMiddleware
|
|
} else {
|
|
// Fallback if threat manager is not available
|
|
roleMiddleware = func(role string) func(http.HandlerFunc) http.HandlerFunc {
|
|
return func(next http.HandlerFunc) http.HandlerFunc { return next }
|
|
}
|
|
}
|
|
|
|
// Web Templates page (Admin only)
|
|
mux.HandleFunc("/webtemplates", authMiddleware(roleMiddleware("admin")(func(w http.ResponseWriter, r *http.Request) {
|
|
data := map[string]any{
|
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
|
"PageTitle": "webtemplates_title",
|
|
"PageContent": "webtemplates_content",
|
|
}
|
|
// Add CSRF token if security manager is available
|
|
if a.threatManager != nil {
|
|
a.threatManager.GetSecurityManager().AddCSRFToken(w, data)
|
|
}
|
|
if templates != nil {
|
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
|
return
|
|
}
|
|
http.Error(w, "templates not loaded", 500)
|
|
})))
|
|
|
|
mux.HandleFunc("/users", authMiddleware(roleMiddleware("admin")(func(w http.ResponseWriter, r *http.Request) {
|
|
// Get current user from context
|
|
var currentUser interface{}
|
|
if a.threatManager != nil {
|
|
currentUser = a.threatManager.GetSecurityManager().GetUserFromContext(r.Context())
|
|
}
|
|
|
|
data := map[string]any{
|
|
"Now": time.Now().Format("2006-01-02 15:04:05 MST"),
|
|
"CurrentUser": currentUser,
|
|
"PageTitle": "users_title",
|
|
"PageContent": "users_content",
|
|
}
|
|
// Add CSRF token if security manager is available
|
|
if a.threatManager != nil {
|
|
a.threatManager.GetSecurityManager().AddCSRFToken(w, data)
|
|
}
|
|
if templates != nil {
|
|
_ = templates.ExecuteTemplate(w, "layout.html", data)
|
|
return
|
|
}
|
|
http.Error(w, "templates not loaded", 500)
|
|
})))
|
|
|
|
srv := &http.Server{Addr: addr, Handler: mux}
|
|
a.addHTTPServer(srv)
|
|
log.Printf("Dashboard listening on http://%s", addr)
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Printf("dashboard error: %v", err)
|
|
}
|
|
}
|
|
func parseTime(s string) (t time.Time) {
|
|
t, _ = time.Parse(time.RFC3339Nano, s)
|
|
if t.IsZero() {
|
|
// fallback current time
|
|
t = time.Now()
|
|
}
|
|
return
|
|
}
|