package main import ( "encoding/json" "flag" "fmt" "os" "time" "git.wntrmute.dev/kyle/mcias/internal/db" "git.wntrmute.dev/kyle/mcias/internal/model" ) func (t *tool) runAudit(args []string) { if len(args) == 0 { fatalf("audit requires a subcommand: tail, query") } switch args[0] { case "tail": t.auditTail(args[1:]) case "query": t.auditQuery(args[1:]) default: fatalf("unknown audit subcommand %q", args[0]) } } func (t *tool) auditTail(args []string) { fs := flag.NewFlagSet("audit tail", flag.ExitOnError) n := fs.Int("n", 50, "number of events to show") asJSON := fs.Bool("json", false, "output as newline-delimited JSON") _ = fs.Parse(args) if *n <= 0 { fatalf("audit tail: --n must be positive") } events, err := t.db.TailAuditEvents(*n) if err != nil { fatalf("tail audit events: %v", err) } printAuditEvents(events, *asJSON) } func (t *tool) auditQuery(args []string) { fs := flag.NewFlagSet("audit query", flag.ExitOnError) accountUUID := fs.String("account", "", "filter by account UUID (actor or target)") eventType := fs.String("type", "", "filter by event type") sinceStr := fs.String("since", "", "filter events on or after this RFC-3339 timestamp") asJSON := fs.Bool("json", false, "output as newline-delimited JSON") _ = fs.Parse(args) p := db.AuditQueryParams{ EventType: *eventType, } if *accountUUID != "" { a, err := t.db.GetAccountByUUID(*accountUUID) if err != nil { fatalf("get account: %v", err) } p.AccountID = &a.ID } if *sinceStr != "" { since, err := time.Parse(time.RFC3339, *sinceStr) if err != nil { fatalf("audit query: --since must be an RFC-3339 timestamp (e.g. 2006-01-02T15:04:05Z): %v", err) } p.Since = &since } events, err := t.db.ListAuditEvents(p) if err != nil { fatalf("query audit events: %v", err) } printAuditEvents(events, *asJSON) } func printAuditEvents(events []*model.AuditEvent, asJSON bool) { if len(events) == 0 { fmt.Println("no audit events found") return } if asJSON { enc := json.NewEncoder(os.Stdout) for _, ev := range events { if err := enc.Encode(ev); err != nil { fatalf("encode audit event: %v", err) } } return } fmt.Printf("%-20s %-22s %-15s %s\n", "TIME", "EVENT TYPE", "IP", "DETAILS") fmt.Println("────────────────────────────────────────────────────────────────────────────────") for _, ev := range events { ip := ev.IPAddress if ip == "" { ip = "-" } details := ev.Details if details == "" { details = "-" } fmt.Printf("%-20s %-22s %-15s %s\n", ev.EventTime.Format("2006-01-02T15:04:05Z"), ev.EventType, ip, details, ) } }