Files
2026-07-22 14:14:53 +03:00

51 lines
1.6 KiB
Go

package audit
import (
"bufio"
"fmt"
"os/exec"
"regexp"
"time"
)
func now() time.Time { return time.Now() }
var (
reAccepted = regexp.MustCompile(`Accepted publickey for (\S+) from (\S+) port \d+`)
reDisconnect = regexp.MustCompile(`Disconnected from user (\S+) (\S+) port \d+`)
reSessClosed = regexp.MustCompile(`session closed for user (\S+)`)
)
// TailJournal runs indefinitely (intended to be run under systemd — see
// scripts/ssh-access-tui-audit.service), following sshd's journal output and
// appending normalized connect/disconnect events to the audit log at
// logPath. This is what makes "who logged in/out" survive independently of
// journald's own retention/rotation policy.
func TailJournal(logPath string) error {
cmd := exec.Command("journalctl", "-u", "ssh", "-u", "sshd", "-f", "-n", "0", "-o", "cat")
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("journalctl pipe: %w", err)
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("start journalctl: %w", err)
}
sc := bufio.NewScanner(stdout)
for sc.Scan() {
line := sc.Text()
switch {
case reAccepted.MatchString(line):
m := reAccepted.FindStringSubmatch(line)
_ = Append(logPath, Event{Time: now(), User: m[1], Action: "connect", SrcIP: m[2]})
case reDisconnect.MatchString(line):
m := reDisconnect.FindStringSubmatch(line)
_ = Append(logPath, Event{Time: now(), User: m[1], Action: "disconnect", SrcIP: m[2]})
case reSessClosed.MatchString(line):
m := reSessClosed.FindStringSubmatch(line)
_ = Append(logPath, Event{Time: now(), User: m[1], Action: "disconnect"})
}
}
return cmd.Wait()
}