penguinpowernz/auditor

Access live auditd events in golang

★ 0Forks 0GoGitHub ↗Compare

README

auditor

A Go library for tailing and parsing Linux audit logs (/var/log/audit/audit.log).

Features

  • Event grouping: automatically groups multi-record audit events by kernel event ID
  • Multiple subscribers: broadcast events to multiple independent consumers
  • Filtering: glob-based filtering across any record field with AND-combined criteria
  • Non-blocking: slow subscribers don't block fast ones
  • Type-safe models: strongly-typed interfaces for specific event types (SYSCALL, PATH, etc.)

Installation

go get github.com/penguinpowernz/auditor

Quick Start

Basic tailing

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/penguinpowernz/auditor"
)

func main() {
    // Create a tailer
    t := auditor.NewTailer("/var/log/audit/audit.log", auditor.Options{
        FlushTimeout: 50 * time.Millisecond,
        PollInterval: 100 * time.Millisecond,
        SeekToEnd:    true, // start from current position (live tail)
    })

    // Subscribe to receive events
    events := t.Subscribe()

    ctx := context.Background()
    go t.Run(ctx)

    // Process events
    for ev := range events {
        fmt.Printf("Event %d at %s: %d records\n", 
            ev.ID, ev.Timestamp, len(ev.Records))
    }
}

Filtering events

// Create filtered subscriber - only root execve calls
filtered := t.Subscribe()

matches := auditor.NewFilter().
    Where(auditor.FieldFilter{
        RecordType: auditor.RecordTypeSyscall,
        Field:      "euid",
        Pattern:    "0", // root
    }).
    Where(auditor.FieldFilter{
        RecordType: auditor.RecordTypeSyscall,
        Field:      "syscall",
        Pattern:    "59", // execve
    }).
    Apply(filtered)

for m := range matches {
    syscall := m.Event.RecordsByType(auditor.RecordTypeSyscall)[0]
    exe := syscall.Fields["exe"]
    fmt.Printf("Root executed: %s\n", exe)
}

Using typed event models

Instead of working with map[string]string fields directly, you can use typed models:

for ev := range events {
    for _, rec := range ev.Records {
        if rec.Type == auditor.RecordTypeSyscall {
            // Get a strongly-typed model for SYSCALL records
            syscall := rec.EventModel().(auditor.SyscallModel)
            
            fmt.Printf("UID: %s, GID: %s, Exe: %s\n",
                syscall.UID(), syscall.GID(), syscall.Exe())
        }
    }
}

Common interfaces for field groups:

// Any record type with user fields
type UserGetter interface {
    UID() string
    GID() string
    EUID() string
    EGID() string
}

// Any record type with process fields
type ProcessGetter interface {
    PID() string
    PPID() string
    Exe() string
    Comm() string
}

// Any record type with path fields
type PathGetter interface {
    Name() string
    Nametype() string
}

Architecture

Events and Records

An audit Event consists of multiple Records grouped by a kernel-assigned event ID:

Event 12345 (timestamp: 2026-04-09 10:30:00)
  ├─ SYSCALL record: syscall=59 (execve), uid=1000, exe="/usr/bin/ls"
  ├─ EXECVE record: argv=["ls", "-la"]
  ├─ CWD record: cwd="/home/user"
  └─ PATH record: name="/usr/bin/ls"

Each record is a single line in the audit log. The tailer automatically groups these by event ID.

Subscriber model

The tailer uses a broadcast pattern:

  • Each call to Subscribe() returns a new channel
  • Every event is sent to every subscriber's channel
  • Subscribers are independent - a slow subscriber can't block others
  • If a subscriber's buffer fills, events are dropped for that subscriber only

Options

type Options struct {
    // How long to wait after first record before flushing the event
    // Default: 50ms
    FlushTimeout time.Duration
    
    // How often to poll the file for new data
    // Default: 100ms
    PollInterval time.Duration
    
    // Start reading from end of file (tail -f behavior)
    // Default: false (read from beginning)
    SeekToEnd bool
    
    // Buffer size for each subscriber's channel
    // Default: 64
    SubscriberBuffer int
}

Field Reference

The library includes comprehensive field documentation. Access it via:

doc := auditor.FieldDoc["uid"]
// "User ID of the process that triggered the event"

Common fields by record type:

SYSCALL records:

  • syscall: syscall number
  • uid, gid: user/group ID
  • euid, egid: effective user/group ID
  • exe: executable path
  • pid, ppid: process ID, parent process ID
  • success: "yes" or "no"

PATH records:

  • name: filesystem path
  • nametype: NORMAL, CREATE, DELETE, etc.
  • mode: file permissions
  • ogid, ouid: owner group/user ID

EXECVE records:

  • a0, a1, a2, ...: command-line arguments

CWD records:

  • cwd: current working directory

Examples

See the examples directory for complete working examples:

Changelog

Recent fixes

  • Tailer now continues reading after EOF: Fixed an issue where the tailer would stop processing events after reaching EOF. The tailer now correctly continues reading when new events are appended to the log file (issue #1).
  • EventModel interface: Added strongly-typed interfaces for accessing record fields, making it easier to work with specific event types without manual map lookups (issue #3).

License

MIT