internal/store/audit.go
75 lines · 2096 bytes
1package store
2
3import "encoding/json"
4
5// Audit appends to the security feed. Events are the product feed; this
6// records who did what, from where, for an operator. actorID 0 means the
7// host admin (gitbayd admin commands) or an unauthenticated source.
8func (s *Store) Audit(actorID int64, action string, data map[string]any) {
9 var actor any
10 if actorID != 0 {
11 actor = actorID
12 }
13 raw, err := json.Marshal(data)
14 if err != nil {
15 raw = []byte("{}")
16 }
17 s.DB.Exec("INSERT INTO audit_log (actor_id, action, data_json) VALUES (?, ?, ?)",
18 actor, action, string(raw))
19}
20
21type AuditEntry struct {
22 ID int64 `json:"id"`
23 Actor string `json:"actor,omitempty"`
24 Action string `json:"action"`
25 Data string `json:"data"`
26 CreatedAt string `json:"created_at"`
27}
28
29// AuditFilter narrows AuditEntries. Actor is a username, or "-" for rows
30// with no actor (host commands, auth failures). ActionPrefix matches the
31// start of the action. Since is an ISO timestamp in the log's own format.
32type AuditFilter struct {
33 Actor string
34 ActionPrefix string
35 Since string
36 Limit int
37}
38
39func (s *Store) AuditEntries(f AuditFilter) ([]AuditEntry, error) {
40 q := `SELECT a.id, COALESCE(u.username, ''), a.action, a.data_json, a.created_at
41 FROM audit_log a LEFT JOIN users u ON u.id = a.actor_id WHERE 1 = 1`
42 var args []any
43 switch f.Actor {
44 case "":
45 case "-":
46 q += " AND a.actor_id IS NULL"
47 default:
48 q += " AND u.username = ?"
49 args = append(args, f.Actor)
50 }
51 if f.ActionPrefix != "" {
52 q += " AND substr(a.action, 1, length(?)) = ?"
53 args = append(args, f.ActionPrefix, f.ActionPrefix)
54 }
55 if f.Since != "" {
56 q += " AND a.created_at >= ?"
57 args = append(args, f.Since)
58 }
59 q += " ORDER BY a.id DESC LIMIT ?"
60 args = append(args, f.Limit)
61 rows, err := s.DB.Query(q, args...)
62 if err != nil {
63 return nil, err
64 }
65 defer rows.Close()
66 var out []AuditEntry
67 for rows.Next() {
68 var e AuditEntry
69 if err := rows.Scan(&e.ID, &e.Actor, &e.Action, &e.Data, &e.CreatedAt); err != nil {
70 return nil, err
71 }
72 out = append(out, e)
73 }
74 return out, rows.Err()
75}