Files
uptop/internal/cluster/cluster.go
T
lerko 986f9f1d55
CI / test (pull_request) Successful in 4m24s
CI / lint (pull_request) Successful in 1m1s
fix(security): phase 4 code quality and low-severity fixes
- Fix limitStr to handle multi-byte UTF-8 characters correctly
- Sanitize log messages: strip ANSI escape sequences and newlines
- URL-encode probe node_id instead of string concatenation
- Fix follower resp.Body leak on non-200 responses
- Make SSH host key path configurable via UPTOP_SSH_HOST_KEY env var
- Add HTTP method checks on GET-only endpoints (405 for wrong methods)
- Extract magic numbers into named constants across monitor/store/server
- Standardize error output to stderr for all startup errors
2026-05-26 17:25:47 -04:00

81 lines
1.9 KiB
Go

package cluster
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"gitea.lerkolabs.com/lerko/uptop/internal/monitor"
)
type Config struct {
Mode string // "leader" or "follower"
PeerURL string // URL of the Leader (e.g., http://primary:8080)
SharedKey string // Security Key
}
func Start(ctx context.Context, cfg Config, eng *monitor.Engine) {
if cfg.Mode == "leader" {
fmt.Println("Cluster: Running as LEADER (Active)")
if cfg.SharedKey != "" {
fmt.Println("WARNING: Cluster mode enabled. Ensure the HTTP server is behind a TLS-terminating proxy.")
}
eng.SetActive(true)
return
}
if cfg.Mode == "follower" {
fmt.Println("Cluster: Running as FOLLOWER (Passive)")
if cfg.PeerURL != "" && !strings.HasPrefix(cfg.PeerURL, "https://") {
fmt.Println("WARNING: Cluster peer URL is not HTTPS. Cluster secret will be sent in cleartext.")
}
eng.SetActive(false)
go runFollowerLoop(ctx, cfg, eng)
}
// "probe" mode is handled directly in main.go before cluster.Start is called
}
func runFollowerLoop(ctx context.Context, cfg Config, eng *monitor.Engine) {
client := http.Client{Timeout: 2 * time.Second}
failures := 0
threshold := 3
for {
select {
case <-time.After(5 * time.Second):
case <-ctx.Done():
return
}
req, _ := http.NewRequest("GET", cfg.PeerURL+"/api/health", nil)
if cfg.SharedKey != "" {
req.Header.Set("X-Upkeep-Secret", cfg.SharedKey)
}
resp, err := client.Do(req)
isLeaderHealthy := false
if err == nil {
isLeaderHealthy = resp.StatusCode == 200
_ = resp.Body.Close()
}
if isLeaderHealthy {
failures = 0
if eng.IsActive() {
eng.SetActive(false)
eng.AddLog("Cluster: Leader detected. Switching to PASSIVE.")
}
} else {
failures++
if failures >= threshold && !eng.IsActive() {
eng.SetActive(true)
eng.AddLog("Cluster: Leader Unreachable. Switching to ACTIVE.")
}
}
}
}