51 lines
1.6 KiB
Go
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()
|
|
}
|