feat: switch macOS learning mode from fs_usage to eslogger
Replace fs_usage (reports Mach thread IDs, requiring process name matching with false positives) with eslogger (Endpoint Security framework, reports real Unix PIDs via audit_token.pid plus fork events for process tree tracking). Key changes: - Daemon starts eslogger instead of fs_usage, with early-exit detection and clear Full Disk Access error messaging - New two-pass eslogger JSON parser: pass 1 builds PID tree from fork events, pass 2 filters filesystem events by PID set - Remove runtime PID polling (StartPIDTracking, pollDescendantPIDs) — process tree is now built post-hoc from the eslogger log - Platform-specific generateLearnedTemplatePlatform() for darwin/linux/stub - Refactor TraceResult and GenerateLearnedTemplate to be platform-agnostic
This commit is contained in:
@@ -30,12 +30,16 @@ type Manager struct {
|
||||
debug bool
|
||||
monitor bool
|
||||
initialized bool
|
||||
learning bool // learning mode: permissive sandbox with strace
|
||||
straceLogPath string // host-side temp file for strace output
|
||||
learning bool // learning mode: permissive sandbox with strace/eslogger
|
||||
straceLogPath string // host-side temp file for strace output (Linux)
|
||||
commandName string // name of the command being learned
|
||||
// macOS daemon session fields
|
||||
daemonClient *daemon.Client
|
||||
daemonSession *DaemonSession
|
||||
// macOS learning mode fields
|
||||
learningID string // daemon learning session ID
|
||||
learningLog string // eslogger log file path
|
||||
learningRootPID int // root PID of the command being learned
|
||||
}
|
||||
|
||||
// NewManager creates a new sandbox manager.
|
||||
@@ -77,6 +81,28 @@ func (m *Manager) Initialize() error {
|
||||
return fmt.Errorf("sandbox is not supported on platform: %s", platform.Detect())
|
||||
}
|
||||
|
||||
// On macOS in learning mode, use the daemon for eslogger tracing only.
|
||||
// No TUN/pf/DNS session needed — the command runs unsandboxed.
|
||||
if platform.Detect() == platform.MacOS && m.learning {
|
||||
client := daemon.NewClient(daemon.DefaultSocketPath, m.debug)
|
||||
if !client.IsRunning() {
|
||||
return fmt.Errorf("greywall daemon is not running (required for macOS learning mode)\n\n" +
|
||||
" Install and start: sudo greywall daemon install\n" +
|
||||
" Check status: greywall daemon status")
|
||||
}
|
||||
m.logDebug("Daemon is running, requesting learning session")
|
||||
resp, err := client.StartLearning()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start learning session: %w", err)
|
||||
}
|
||||
m.daemonClient = client
|
||||
m.learningID = resp.LearningID
|
||||
m.learningLog = resp.LearningLog
|
||||
m.logDebug("Learning session started: id=%s log=%s", m.learningID, m.learningLog)
|
||||
m.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// On macOS, the daemon is required for transparent proxying.
|
||||
// Without it, env-var proxying is unreliable (only works for tools that
|
||||
// honor HTTP_PROXY) and gives users a false sense of security.
|
||||
@@ -187,6 +213,10 @@ func (m *Manager) WrapCommand(command string) (string, error) {
|
||||
plat := platform.Detect()
|
||||
switch plat {
|
||||
case platform.MacOS:
|
||||
if m.learning {
|
||||
// In learning mode, run command directly (no sandbox-exec wrapping)
|
||||
return command, nil
|
||||
}
|
||||
return WrapCommandMacOS(m.config, command, m.exposedPorts, m.daemonSession, m.debug)
|
||||
case platform.Linux:
|
||||
if m.learning {
|
||||
@@ -220,26 +250,30 @@ func (m *Manager) wrapCommandLearning(command string) (string, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// GenerateLearnedTemplate generates a config template from the strace log collected during learning.
|
||||
// GenerateLearnedTemplate generates a config template from the trace log collected during learning.
|
||||
// Platform-specific implementation in manager_linux.go / manager_darwin.go.
|
||||
func (m *Manager) GenerateLearnedTemplate(cmdName string) (string, error) {
|
||||
if m.straceLogPath == "" {
|
||||
return "", fmt.Errorf("no strace log available (was learning mode enabled?)")
|
||||
}
|
||||
return m.generateLearnedTemplatePlatform(cmdName)
|
||||
}
|
||||
|
||||
templatePath, err := GenerateLearnedTemplate(m.straceLogPath, cmdName, m.debug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Clean up strace log since we've processed it
|
||||
_ = os.Remove(m.straceLogPath)
|
||||
m.straceLogPath = ""
|
||||
|
||||
return templatePath, nil
|
||||
// SetLearningRootPID records the root PID of the command being learned.
|
||||
// The eslogger log parser uses this to build the process tree from fork events.
|
||||
func (m *Manager) SetLearningRootPID(pid int) {
|
||||
m.learningRootPID = pid
|
||||
m.logDebug("Set learning root PID: %d", pid)
|
||||
}
|
||||
|
||||
// Cleanup stops the proxies and cleans up resources.
|
||||
func (m *Manager) Cleanup() {
|
||||
// Stop macOS learning session if active
|
||||
if m.daemonClient != nil && m.learningID != "" {
|
||||
m.logDebug("Stopping learning session %s", m.learningID)
|
||||
if err := m.daemonClient.StopLearning(m.learningID); err != nil {
|
||||
m.logDebug("Warning: failed to stop learning session: %v", err)
|
||||
}
|
||||
m.learningID = ""
|
||||
}
|
||||
|
||||
// Destroy macOS daemon session if active.
|
||||
if m.daemonClient != nil && m.daemonSession != nil {
|
||||
m.logDebug("Destroying daemon session %s", m.daemonSession.SessionID)
|
||||
@@ -247,9 +281,11 @@ func (m *Manager) Cleanup() {
|
||||
m.logDebug("Warning: failed to destroy daemon session: %v", err)
|
||||
}
|
||||
m.daemonSession = nil
|
||||
m.daemonClient = nil
|
||||
}
|
||||
|
||||
// Clear daemon client after all daemon interactions
|
||||
m.daemonClient = nil
|
||||
|
||||
if m.reverseBridge != nil {
|
||||
m.reverseBridge.Cleanup()
|
||||
}
|
||||
@@ -266,6 +302,10 @@ func (m *Manager) Cleanup() {
|
||||
_ = os.Remove(m.straceLogPath)
|
||||
m.straceLogPath = ""
|
||||
}
|
||||
if m.learningLog != "" {
|
||||
_ = os.Remove(m.learningLog)
|
||||
m.learningLog = ""
|
||||
}
|
||||
m.logDebug("Sandbox manager cleaned up")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user