Implement audit enrichments, TUI workflows, and production ISO scaffold
This commit is contained in:
@@ -6,7 +6,11 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"bee/audit/internal/collector"
|
||||
)
|
||||
@@ -71,8 +75,93 @@ func writeOutput(dest string, data []byte) error {
|
||||
// writeToUSB auto-detects the first removable block device, mounts it,
|
||||
// and writes the audit JSON. Falls back to /tmp on any failure.
|
||||
func writeToUSB(data []byte) error {
|
||||
// implemented in step 1.11
|
||||
slog.Warn("usb output not yet implemented, falling back to stdout")
|
||||
_, err := os.Stdout.Write(append(data, '\n'))
|
||||
return err
|
||||
boardSerial := extractBoardSerial(data)
|
||||
filename := auditFilename(boardSerial, time.Now().UTC())
|
||||
|
||||
device, err := firstRemovableDevice()
|
||||
if err != nil {
|
||||
slog.Warn("usb output: no removable device, writing to /tmp", "err", err)
|
||||
return writeAuditToPath(filepath.Join("/tmp", filename), data)
|
||||
}
|
||||
|
||||
mountpoint := "/tmp/bee-usb"
|
||||
if err := os.MkdirAll(mountpoint, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := exec.Command("mount", device, mountpoint).Run(); err != nil {
|
||||
slog.Warn("usb output: mount failed, writing to /tmp", "device", device, "err", err)
|
||||
return writeAuditToPath(filepath.Join("/tmp", filename), data)
|
||||
}
|
||||
defer func() {
|
||||
if err := exec.Command("umount", mountpoint).Run(); err != nil {
|
||||
slog.Warn("usb output: umount failed", "mountpoint", mountpoint, "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
path := filepath.Join(mountpoint, filename)
|
||||
if err := writeAuditToPath(path, data); err != nil {
|
||||
slog.Warn("usb output: write failed, falling back to /tmp", "path", path, "err", err)
|
||||
return writeAuditToPath(filepath.Join("/tmp", filename), data)
|
||||
}
|
||||
|
||||
slog.Info("usb output: written", "path", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeAuditToPath(path string, data []byte) error {
|
||||
if err := os.WriteFile(path, append(data, '\n'), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
slog.Info("audit output written", "path", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractBoardSerial(data []byte) string {
|
||||
var doc struct {
|
||||
Hardware struct {
|
||||
Board struct {
|
||||
SerialNumber string `json:"serial_number"`
|
||||
} `json:"board"`
|
||||
} `json:"hardware"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &doc); err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
serial := strings.TrimSpace(doc.Hardware.Board.SerialNumber)
|
||||
if serial == "" {
|
||||
return "unknown"
|
||||
}
|
||||
return serial
|
||||
}
|
||||
|
||||
func auditFilename(boardSerial string, now time.Time) string {
|
||||
boardSerial = strings.TrimSpace(boardSerial)
|
||||
if boardSerial == "" {
|
||||
boardSerial = "unknown"
|
||||
}
|
||||
return fmt.Sprintf("audit-%s-%s.json", boardSerial, now.Format("20060102-150405"))
|
||||
}
|
||||
|
||||
func firstRemovableDevice() (string, error) {
|
||||
entries, err := os.ReadDir("/sys/block")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sort.Slice(entries, func(i, j int) bool { return entries[i].Name() < entries[j].Name() })
|
||||
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if strings.HasPrefix(name, "loop") || strings.HasPrefix(name, "ram") {
|
||||
continue
|
||||
}
|
||||
removableFlag, err := os.ReadFile(filepath.Join("/sys/block", name, "removable"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(string(removableFlag)) == "1" {
|
||||
return filepath.Join("/dev", name), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no removable block device found")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user