feat: robust username detection and CI updates

Add platform-specific username detection for Control D metadata:
- macOS: directory services (dscl) with console user fallback
- Linux: systemd loginctl, utmp, /etc/passwd traversal
- Windows: WTS session enumeration, registry, token lookup
This commit is contained in:
Codescribe
2026-03-03 02:07:11 -05:00
committed by Cuong Manh Le
parent e7040bd9f9
commit 9b2e51f53a
7 changed files with 832 additions and 53 deletions

View File

@@ -2,11 +2,6 @@ package ctrld
import (
"context"
"os"
"os/exec"
"os/user"
"runtime"
"strings"
"github.com/cuonglm/osinfo"
@@ -27,8 +22,21 @@ var (
chassisVendor string
)
// SystemMetadata collects system and user-related SystemMetadata and returns it as a map.
// SystemMetadata collects full system metadata including username discovery.
// Use for initial provisioning and first-run config validation where full
// device identification is needed.
func SystemMetadata(ctx context.Context) map[string]string {
return systemMetadata(ctx, true)
}
// SystemMetadataRuntime collects system metadata without username discovery.
// Use for runtime API calls (config reload, self-uninstall check, deactivation
// pin refresh) to avoid repeated user enumeration that can trigger EDR alerts.
func SystemMetadataRuntime(ctx context.Context) map[string]string {
return systemMetadata(ctx, false)
}
func systemMetadata(ctx context.Context, includeUsername bool) map[string]string {
m := make(map[string]string)
oi := osinfo.New()
m[metadataOsKey] = oi.String()
@@ -39,7 +47,9 @@ func SystemMetadata(ctx context.Context) map[string]string {
}
m[metadataChassisTypeKey] = chassisType
m[metadataChassisVendorKey] = chassisVendor
m[metadataUsernameKey] = currentLoginUser(ctx)
if includeUsername {
m[metadataUsernameKey] = DiscoverMainUser(ctx)
}
m[metadataDomainOrWorkgroupKey] = partOfDomainOrWorkgroup(ctx)
domain, err := system.GetActiveDirectoryDomain()
if err != nil {
@@ -49,45 +59,3 @@ func SystemMetadata(ctx context.Context) map[string]string {
return m
}
// currentLoginUser attempts to find the actual login user, even if the process is running as root.
func currentLoginUser(ctx context.Context) string {
// On Darwin 26.2+, sudo no longer preserves SUDO_USER, LOGNAME, USER etc., so we cannot
// rely on environment variables when running under sudo. See CVE-2025-43416.
// We use the logname(1) command on Unix, which reports the login name from the session
// (e.g. utmp); there is no portable syscall equivalent in Go, so we exec logname.
if runtime.GOOS != "windows" {
if name := runLogname(ctx); name != "" {
return name
}
}
// Fallback: env vars (still set on older systems or when not using sudo)
if u := os.Getenv("SUDO_USER"); u != "" {
return u
}
if u := os.Getenv("LOGNAME"); u != "" {
return u
}
if u := os.Getenv("USER"); u != "" {
return u
}
currentUser, err := user.Current()
if err != nil {
ProxyLogger.Load().Debug().Err(err).Msg("Failed to get current user")
return "unknown"
}
return currentUser.Username
}
// runLogname runs the logname(1) command and returns the trimmed output, or "" on failure.
func runLogname(ctx context.Context) string {
cmd := exec.CommandContext(ctx, "logname")
out, err := cmd.Output()
if err != nil {
ProxyLogger.Load().Debug().Err(err).Msg("Failed to run logname")
return ""
}
return strings.TrimSpace(string(out))
}