Claude Code session 1.

This commit is contained in:
Kalzu Rekku
2026-01-08 12:11:26 +02:00
parent c59523060d
commit 6db2e58dcd
20 changed files with 5497 additions and 83 deletions

View File

@@ -33,6 +33,10 @@ var (
m map[string]*Session
}{m: make(map[string]*Session)}
logger *Logger
// Rate limiters
authRateLimiter *RateLimiter // Aggressive limit for auth endpoints
apiRateLimiter *RateLimiter // Moderate limit for API endpoints
)
type Session struct {
@@ -49,6 +53,7 @@ func main() {
dyfiPass := flag.String("dyfi-pass", os.Getenv("DYFI_PASS"), "dy.fi password")
email := flag.String("email", os.Getenv("ACME_EMAIL"), "Email for Let's Encrypt notifications")
logFile := flag.String("log", os.Getenv("LOG_FILE"), "Path to log file for fail2ban")
enableGateway := flag.Bool("enable-gateway", false, "Enable gateway/proxy mode for external workers")
flag.Parse()
@@ -76,6 +81,28 @@ func main() {
store = NewUserStore("users_data", crypto)
// Initialize worker store and health poller
workerStore = NewWorkerStore("workers_data.json")
healthPoller = NewHealthPoller(workerStore, 60*time.Second)
healthPoller.Start()
logger.Info("Worker health poller started (60s interval)")
// Initialize gateway components (if enabled)
if *enableGateway {
apiKeyStore = NewAPIKeyStore("apikeys_data", crypto)
proxyManager = NewProxyManager(workerStore)
logger.Info("Gateway mode enabled - API key auth and proxy available")
} else {
logger.Info("Gateway mode disabled (use --enable-gateway to enable)")
}
// Initialize rate limiters
// Auth endpoints: 10 requests per minute (aggressive)
authRateLimiter = NewRateLimiter(10, 1*time.Minute)
// API endpoints: 100 requests per minute (moderate)
apiRateLimiter = NewRateLimiter(100, 1*time.Minute)
logger.Info("Rate limiters initialized (auth: 10/min, api: 100/min)")
// --- BACKGROUND TASKS ---
// Reload user store from disk periodically
go func() {
@@ -97,7 +124,7 @@ func main() {
// dy.fi Dynamic DNS Updater
if *domain != "" && *dyfiUser != "" {
startDyfiUpdater(*domain, *dyfiUser, *dyfiPass)
startDyfiUpdater(*domain, *dyfiUser, *dyfiPass, *port)
}
// --- CLI COMMANDS ---
@@ -119,6 +146,13 @@ func main() {
// --- ROUTES ---
// Routes must be defined BEFORE the server starts
// Public health endpoint (no auth required) for monitoring and dy.fi failover
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"healthy"}`))
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if session := getValidSession(r, crypto); session != nil {
http.Redirect(w, r, "/app", http.StatusSeeOther)
@@ -128,6 +162,25 @@ func main() {
})
http.HandleFunc("/app", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
// Redirect to dashboard
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
})
http.HandleFunc("/dashboard", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
handleDashboard(w, r)
})
http.HandleFunc("/rest-client", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
@@ -152,6 +205,47 @@ func main() {
http.Redirect(w, r, "/", http.StatusSeeOther)
})
// API: Worker management endpoints
http.HandleFunc("/api/workers/list", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
return
}
handleAPIWorkersList(w, r)
})
http.HandleFunc("/api/workers/register", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
return
}
handleAPIWorkersRegister(w, r)
})
http.HandleFunc("/api/workers/remove", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
return
}
handleAPIWorkersRemove(w, r)
})
http.HandleFunc("/api/workers/get", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
return
}
handleAPIWorkersGet(w, r)
})
http.HandleFunc("/api/request", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
@@ -177,8 +271,64 @@ func main() {
json.NewEncoder(w).Encode(result)
})
http.HandleFunc("/verify-user", func(w http.ResponseWriter, r *http.Request) {
// Gateway endpoints (API key auth) - only if gateway is enabled
if *enableGateway {
http.HandleFunc("/api/gateway/target", APIKeyAuthMiddleware(apiKeyStore, handleGatewayTarget))
http.HandleFunc("/api/gateway/result", APIKeyAuthMiddleware(apiKeyStore, handleGatewayResult))
http.HandleFunc("/api/gateway/stats", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
return
}
handleGatewayStats(w, r)
})
// API key management endpoints (TOTP auth - admin only)
http.HandleFunc("/api/apikeys/generate", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
return
}
handleAPIKeyGenerate(w, r)
})
http.HandleFunc("/api/apikeys/list", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
return
}
handleAPIKeyList(w, r)
})
http.HandleFunc("/api/apikeys/revoke", func(w http.ResponseWriter, r *http.Request) {
session := getValidSession(r, crypto)
if session == nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
return
}
handleAPIKeyRevoke(w, r)
})
logger.Info("Gateway routes registered")
}
http.HandleFunc("/verify-user", RateLimitMiddleware(authRateLimiter, func(w http.ResponseWriter, r *http.Request) {
userID := strings.TrimSpace(r.FormValue("userid"))
// Input validation
if !ValidateInput(userID, 100) {
logger.Warn("AUTH_FAILURE: Invalid user ID format from IP %s", getIP(r))
tmpl.Execute(w, map[string]interface{}{"Step2": false, "Error": "Invalid input"})
return
}
user, err := store.GetUser(userID)
if err != nil || user == nil {
// FAIL2BAN TRIGGER
@@ -204,9 +354,9 @@ func main() {
SameSite: http.SameSiteStrictMode,
})
tmpl.Execute(w, map[string]interface{}{"Step2": true})
})
}))
http.HandleFunc("/verify-totp", func(w http.ResponseWriter, r *http.Request) {
http.HandleFunc("/verify-totp", RateLimitMiddleware(authRateLimiter, func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("temp_session")
if err != nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
@@ -226,6 +376,13 @@ func main() {
user, _ := store.GetUser(session.UserID)
totpCode := strings.TrimSpace(r.FormValue("totp"))
// Input validation for TOTP code
if !ValidateInput(totpCode, 10) {
logger.Warn("AUTH_FAILURE: Invalid TOTP format for user %s from IP %s", session.UserID, getIP(r))
tmpl.Execute(w, map[string]interface{}{"Step2": true, "Error": "Invalid input"})
return
}
// Validate the TOTP code
if !totp.Validate(totpCode, user.TOTPSecret) {
// --- FAIL2BAN TRIGGER ---
@@ -260,7 +417,7 @@ func main() {
// Redirect to the main application
http.Redirect(w, r, "/app", http.StatusSeeOther)
})
}))
// --- SERVER STARTUP ---
@@ -280,12 +437,38 @@ func main() {
log.Fatal(http.ListenAndServe(":80", certManager.HTTPHandler(nil)))
}()
// Create base handler with security headers and size limits
baseHandler := SecurityHeadersMiddleware(
MaxBytesMiddleware(10*1024*1024, http.DefaultServeMux), // 10MB max request size
)
// Configure TLS with strong cipher suites
tlsConfig := certManager.TLSConfig()
tlsConfig.MinVersion = tls.VersionTLS12
tlsConfig.PreferServerCipherSuites = true
tlsConfig.CipherSuites = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
}
server := &http.Server{
Addr: ":" + *port,
TLSConfig: certManager.TLSConfig(),
Addr: ":" + *port,
Handler: baseHandler,
TLSConfig: tlsConfig,
ReadTimeout: 15 * time.Second, // Time to read request headers + body
WriteTimeout: 30 * time.Second, // Time to write response
IdleTimeout: 120 * time.Second, // Time to keep connection alive
// Protect against slowloris attacks
ReadHeaderTimeout: 5 * time.Second,
MaxHeaderBytes: 1 << 20, // 1MB max header size
}
logger.Info("Secure Server starting with Let's Encrypt on https://%s", *domain)
logger.Info("Security: Rate limiting enabled, headers hardened, timeouts configured")
log.Fatal(server.ListenAndServeTLS("", "")) // Certs provided by autocert
} else {
// Fallback to Self-Signed Certs
@@ -295,14 +478,35 @@ func main() {
log.Fatal(err)
}
// Create base handler with security headers and size limits
baseHandler := SecurityHeadersMiddleware(
MaxBytesMiddleware(10*1024*1024, http.DefaultServeMux), // 10MB max request size
)
server := &http.Server{
Addr: ":" + *port,
Addr: ":" + *port,
Handler: baseHandler,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
MinVersion: tls.VersionTLS12,
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
},
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
MaxHeaderBytes: 1 << 20, // 1MB
}
logger.Info("Secure Server starting with self-signed certs on https://localhost:%s", *port)
logger.Info("Security: Rate limiting enabled, headers hardened, timeouts configured")
log.Fatal(server.ListenAndServeTLS(certFile, keyFile))
}
}