49 lines
1.3 KiB
Go
49 lines
1.3 KiB
Go
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: <user> [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
|
|
}
|