feat(windows): WireGuard client, auto-VPN, IAM, tray enrichment, installer, CI
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).
This commit is contained in:
@@ -116,23 +116,32 @@ func sleepHost() {
|
||||
}
|
||||
}
|
||||
|
||||
// logoutSession logs off the active console session, or the session of the
|
||||
// logoutSession logs off the active console session, or every session of the
|
||||
// given user (matched by WTS session enumeration).
|
||||
func logoutSession(user string) {
|
||||
sessionID := activeConsoleSessionID()
|
||||
if user != "" {
|
||||
id, err := sessionForUser(user)
|
||||
var ids []uint32
|
||||
if user == "" {
|
||||
if id := activeConsoleSessionID(); id != 0 {
|
||||
ids = []uint32{id}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
ids, err = sessionsForUser(user)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "logout: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
sessionID = id
|
||||
}
|
||||
if sessionID == 0 {
|
||||
if len(ids) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "logout: no active session to log off")
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, id := range ids {
|
||||
logoffSession(id)
|
||||
}
|
||||
}
|
||||
|
||||
func logoffSession(sessionID uint32) {
|
||||
r, _, err := procWTSLogoffSession.Call(wtsCurrentServerHandle, uintptr(sessionID), 0)
|
||||
if r == 0 {
|
||||
fmt.Fprintf(os.Stderr, "WTSLogoffSession(%d) failed: %v\n", sessionID, err)
|
||||
@@ -146,25 +155,30 @@ func activeConsoleSessionID() uint32 {
|
||||
return uint32(id)
|
||||
}
|
||||
|
||||
func sessionForUser(user string) (uint32, error) {
|
||||
// sessionsForUser returns every session whose logged-in user matches.
|
||||
func sessionsForUser(user string) ([]uint32, error) {
|
||||
var pInfo *wtsSessionInfo
|
||||
var count uint32
|
||||
|
||||
r, _, err := procWTSEnumerateSessionsW.Call(wtsCurrentServerHandle, 0, 1, uintptr(unsafe.Pointer(&pInfo)), uintptr(unsafe.Pointer(&count)))
|
||||
if r == 0 {
|
||||
return 0, fmt.Errorf("WTSEnumerateSessionsW: %v", err)
|
||||
return nil, fmt.Errorf("WTSEnumerateSessionsW: %v", err)
|
||||
}
|
||||
defer procWTSFreeMemory.Call(uintptr(unsafe.Pointer(pInfo)))
|
||||
|
||||
want := strings.ToLower(user)
|
||||
var ids []uint32
|
||||
for i := uint32(0); i < count; i++ {
|
||||
info := (*wtsSessionInfo)(unsafe.Pointer(uintptr(unsafe.Pointer(pInfo)) + uintptr(i)*unsafe.Sizeof(*pInfo)))
|
||||
name := wtsSessionUsername(info.SessionID)
|
||||
if name != "" && strings.ToLower(name) == want {
|
||||
return info.SessionID, nil
|
||||
ids = append(ids, info.SessionID)
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("no active session for user %q", user)
|
||||
if len(ids) == 0 {
|
||||
return nil, fmt.Errorf("no active session for user %q", user)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func wtsSessionUsername(sessionID uint32) string {
|
||||
|
||||
@@ -111,6 +111,8 @@ var (
|
||||
mAutoVPN *systray.MenuItem
|
||||
mVPNToggle *systray.MenuItem
|
||||
mSeparator *systray.MenuItem
|
||||
mOpenConfig *systray.MenuItem
|
||||
mReinit *systray.MenuItem
|
||||
mQuit *systray.MenuItem
|
||||
|
||||
currentStatus TrayStatus
|
||||
@@ -130,7 +132,9 @@ func onReady() {
|
||||
mAutoVPN = systray.AddMenuItemCheckbox("Auto-connect VPN when away", "Automatically connect to home via WireGuard when not on the home LAN", false)
|
||||
mVPNToggle = systray.AddMenuItem("Connect VPN", "Manually connect or disconnect the WireGuard tunnel")
|
||||
systray.AddSeparator()
|
||||
mQuit = systray.AddMenuItem("Quit Tray", "Exit the tray icon (daemon keeps running)")
|
||||
mOpenConfig = systray.AddMenuItem("Open Config", "Open agent.yml in the default editor")
|
||||
mReinit = systray.AddMenuItem("Clear enrollment…", "Blank auth_token/public_key so the agent re-enrolls on reconnect")
|
||||
mQuit = systray.AddMenuItem("Quit Tray", "Exit the tray icon (daemon keeps running)")
|
||||
|
||||
// ── IPC loop — connect with retry ──
|
||||
go connectWithRetry()
|
||||
@@ -156,6 +160,12 @@ func onReady() {
|
||||
sendCmd(TrayCommand{Command: "vpn_connect"})
|
||||
}
|
||||
|
||||
case <-mOpenConfig.ClickedCh:
|
||||
sendCmd(TrayCommand{Command: "open_config"})
|
||||
|
||||
case <-mReinit.ClickedCh:
|
||||
sendCmd(TrayCommand{Command: "reinit"})
|
||||
|
||||
case <-mQuit.ClickedCh:
|
||||
systray.Quit()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user