e613874de5
Second milestone of the Windows parity work (DESIGN-WINDOWS.md §13). WireGuard mesh client (signed, WSS-delivered): - wireguard_apply / wireguard_remove commands: Ed25519-verified, gated on a new wireguard capability. Linux applies via wg-quick up/down; Windows installs the peer config as a WireGuardTunnel service via wireguard.exe (/installtunnelservice, /uninstalltunnelservice) - state polling in the home monitor drives the blue tray icon and auto-VPN: connect when away from home + auto_vpn, disconnect on return (2m cooldown) - tray VPN toggle and the auto-VPN checkbox are now live; the preference persists to agent.yml (PersistAutoVPN) IAM on Windows (iam_windows.go): - allowed_login_groups -> net localgroup; ssh_keys -> per-profile authorized_keys + %ProgramData%\ssh\administrators_authorized_keys; revoke_users -> helper logs off all of the user's WTS sessions; sudo_rules logged as no direct equivalent Tray enrichment: - Open Config (opens agent.yml), Clear enrollment (re-enroll) menu items - set_auto_vpn persists; vpn_connect/vpn_disconnect/reinit/open_config commands handled by the daemon (tray_server.go) Packaging & release: - installer/windows/installer.iss: fully-offline Inno Setup bundle (agent, tray, helper, vendor-signed WireGuard MSI, OpenCredential CP, VC++ redist), /SILENT /SERVER_URL /JOIN_KEY parameters, SYSTEM service + HKLM Run tray autostart, Users-writable %ProgramData%\Theta42 for the IPC socket - .github/workflows/build-windows.yml: build + test, pinned vendor downloads, ISCC compile, Azure Trusted Signing (OIDC), SHA256SUMS, GH release attach, optional SSO resource publish - agent.yml.example documents auto_vpn, wireguard, service_name, desktop_helper, public_ip_detect Tests: - wireguard_apply/remove dispatch (allowed + capability-denied), PersistAutoVPN, ClearEnrollment; dispatch tests pin linuxPlatformOps with a temp WireGuard conf - end-to-end verified against a local mock SSO on Windows: join-key enrollment (token persisted, join key blanked), discovery/telemetry pushed, signed arbitrary_bash verified + executed via powershell -EncodedCommand; tray IPC socket binds %ProgramData%\Theta42; LDAP byte-pump binds 127.0.0.1:389; helper update swap verified Rebuilds all tracked dist binaries (v2.1.0).
105 lines
2.7 KiB
Go
105 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
)
|
|
|
|
// wsConnected is flipped atomically by connectWebSocket as the connection
|
|
// comes up and drops, so StartHomeMonitor can read it without a mutex.
|
|
var wsConnected atomic.Bool
|
|
|
|
// agentStopCh closes once the agent should exit: a SIGINT/SIGTERM in the
|
|
// foreground, or the SCM stop request when running as a Windows service. Both
|
|
// runAgent (foreground) and the service handler wait on it.
|
|
var (
|
|
agentStopOnce sync.Once
|
|
agentStopCh = make(chan struct{})
|
|
)
|
|
|
|
// currentCM lets the tray IPC server persist preferences (auto_vpn) and reset
|
|
// enrollment into the live config file. Set once in runAgent.
|
|
var currentCM *ConfigManager
|
|
|
|
// stopAgent signals the running agent to shut down. Idempotent.
|
|
func stopAgent() {
|
|
agentStopOnce.Do(func() { close(agentStopCh) })
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) > 1 && handleCLI(os.Args[1:]) {
|
|
return
|
|
}
|
|
|
|
// Windows: when installed as a service the SCM starts us and svc.Run takes
|
|
// over the process lifecycle. Foreground (or any other OS) falls through to
|
|
// runAgent, which blocks until a signal arrives.
|
|
if maybeRunAsService() {
|
|
return
|
|
}
|
|
|
|
runAgent()
|
|
}
|
|
|
|
// runAgent runs the agent daemon until stopAgent is called.
|
|
func runAgent() {
|
|
log.Println("Starting Theta Agent...")
|
|
|
|
// Attempt to load configuration
|
|
configPath := defaultConfigPath()
|
|
if len(os.Args) > 1 && !strings.HasPrefix(os.Args[1], "-") {
|
|
configPath = os.Args[1]
|
|
}
|
|
|
|
cm, err := NewConfigManager(configPath)
|
|
if err != nil {
|
|
log.Fatalf("Error loading configuration from %s: %v", configPath, err)
|
|
}
|
|
cfg := cm.Get()
|
|
|
|
log.Printf("Connecting to SSO Manager at %s", cfg.ServerURL)
|
|
log.Printf("Loaded capabilities: Telemetry=%v, LDAP=%v, Reboot=%v, Bash=%v",
|
|
cfg.Capabilities.Telemetry,
|
|
cfg.Capabilities.ConfigureLDAP,
|
|
cfg.Capabilities.Reboot,
|
|
cfg.Capabilities.ArbitraryBash,
|
|
)
|
|
|
|
// Initialize system executor and pin the platform ops the command
|
|
// dispatcher runs behind.
|
|
exec := &SystemExecutor{}
|
|
defaultPlatformOps = NewPlatformOps(cfg, exec)
|
|
currentCM = cm
|
|
|
|
// Seed the auto-VPN preference from disk; the tray checkbox updates it.
|
|
SetAutoVPN(cfg.AutoVPN)
|
|
|
|
// Tray IPC server — desktop tray connects here for status updates.
|
|
go globalTrayServer.Start()
|
|
|
|
// WebSocket connection to SSO Manager
|
|
go connectWebSocket(cm, exec)
|
|
|
|
// Home detection + tray status push (polls public IP every 60s).
|
|
go StartHomeMonitor(cfg, func() bool { return wsConnected.Load() })
|
|
|
|
// Foreground: exit on SIGINT/SIGTERM. A Windows service ignores these and
|
|
// is driven by its own handler.
|
|
go func() {
|
|
sigs := make(chan os.Signal, 1)
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
<-sigs
|
|
stopAgent()
|
|
}()
|
|
|
|
<-agentStopCh
|
|
|
|
fmt.Println("Shutting down Theta Agent...")
|
|
}
|