package audit import ( "fmt" "os/exec" "strconv" "strings" ) // OnlineSession is one currently-connected sshd process for a managed user. type OnlineSession struct { User string PID int Elapsed string // e.g. "01:23:45" as reported by ps etime } // ListOnline reports users currently connected. Port-forwarding-only // sessions never allocate a pty, so they are invisible to `who`/`loginctl`; // what *is* always true is that an authenticated connection keeps a // "sshd: [priv]"-style privilege-separated process alive for as long // as the session lasts, so we read that from `ps` instead. func ListOnline(managedUsers map[string]bool) ([]OnlineSession, error) { out, err := exec.Command("ps", "-eo", "user:32,pid,etime,args", "--no-headers").Output() if err != nil { return nil, fmt.Errorf("ps: %w", err) } var sessions []OnlineSession for _, line := range strings.Split(string(out), "\n") { fields := strings.Fields(line) if len(fields) < 4 { continue } user, pidStr, etime := fields[0], fields[1], fields[2] args := strings.Join(fields[3:], " ") if !strings.Contains(args, "sshd:") { continue } if !managedUsers[user] { continue } pid, err := strconv.Atoi(pidStr) if err != nil { continue } sessions = append(sessions, OnlineSession{User: user, PID: pid, Elapsed: etime}) } return sessions, nil }