125 lines
2.8 KiB
Go
125 lines
2.8 KiB
Go
package trust
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// AuditEntry records a single policy evaluation for compliance.
|
|
type AuditEntry struct {
|
|
// Timestamp is when the evaluation occurred.
|
|
Timestamp time.Time `json:"timestamp"`
|
|
// Agent is the name of the agent being evaluated.
|
|
Agent string `json:"agent"`
|
|
// Cap is the capability that was evaluated.
|
|
Cap Capability `json:"capability"`
|
|
// Repo is the repo context (empty if not repo-scoped).
|
|
Repo string `json:"repo,omitempty"`
|
|
// Decision is the evaluation outcome.
|
|
Decision Decision `json:"decision"`
|
|
// Reason is the human-readable reason for the decision.
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
// MarshalJSON implements custom JSON encoding for Decision.
|
|
func (d Decision) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(d.String())
|
|
}
|
|
|
|
// UnmarshalJSON implements custom JSON decoding for Decision.
|
|
func (d *Decision) UnmarshalJSON(data []byte) error {
|
|
var s string
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
switch s {
|
|
case "deny":
|
|
*d = Deny
|
|
case "allow":
|
|
*d = Allow
|
|
case "needs_approval":
|
|
*d = NeedsApproval
|
|
default:
|
|
return fmt.Errorf("trust: unknown decision %q", s)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AuditLog is an append-only log of policy evaluations.
|
|
type AuditLog struct {
|
|
mu sync.Mutex
|
|
entries []AuditEntry
|
|
writer io.Writer
|
|
}
|
|
|
|
// NewAuditLog creates an in-memory audit log. If a writer is provided,
|
|
// each entry is also written as a JSON line to that writer (append-only).
|
|
func NewAuditLog(w io.Writer) *AuditLog {
|
|
return &AuditLog{
|
|
writer: w,
|
|
}
|
|
}
|
|
|
|
// Record appends an evaluation result to the audit log.
|
|
func (l *AuditLog) Record(result EvalResult, repo string) error {
|
|
entry := AuditEntry{
|
|
Timestamp: time.Now(),
|
|
Agent: result.Agent,
|
|
Cap: result.Cap,
|
|
Repo: repo,
|
|
Decision: result.Decision,
|
|
Reason: result.Reason,
|
|
}
|
|
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
l.entries = append(l.entries, entry)
|
|
|
|
if l.writer != nil {
|
|
data, err := json.Marshal(entry)
|
|
if err != nil {
|
|
return fmt.Errorf("trust.AuditLog.Record: marshal failed: %w", err)
|
|
}
|
|
data = append(data, '\n')
|
|
if _, err := l.writer.Write(data); err != nil {
|
|
return fmt.Errorf("trust.AuditLog.Record: write failed: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Entries returns a snapshot of all audit entries.
|
|
func (l *AuditLog) Entries() []AuditEntry {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
out := make([]AuditEntry, len(l.entries))
|
|
copy(out, l.entries)
|
|
return out
|
|
}
|
|
|
|
// Len returns the number of entries in the log.
|
|
func (l *AuditLog) Len() int {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
return len(l.entries)
|
|
}
|
|
|
|
// EntriesFor returns all audit entries for a specific agent.
|
|
func (l *AuditLog) EntriesFor(agent string) []AuditEntry {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
var out []AuditEntry
|
|
for _, e := range l.entries {
|
|
if e.Agent == agent {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out
|
|
}
|