Public source validation / validate (push) Failing after 3m8s
86 lines
2.3 KiB
Go
86 lines
2.3 KiB
Go
package audit
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
type Event struct {
|
|
ID string
|
|
Actor string
|
|
Action string
|
|
ResourceType string
|
|
ResourceID string
|
|
Result string
|
|
OccurredAt time.Time
|
|
CorrelationID string
|
|
Before map[string]any
|
|
After map[string]any
|
|
}
|
|
|
|
type Store interface {
|
|
Append(context.Context, Event) error
|
|
}
|
|
|
|
type MemoryStore struct {
|
|
Events []Event
|
|
}
|
|
|
|
func (store *MemoryStore) Append(_ context.Context, event Event) error {
|
|
if event.ID == "" {
|
|
event.ID = newID()
|
|
}
|
|
if event.OccurredAt.IsZero() {
|
|
event.OccurredAt = time.Now().UTC()
|
|
}
|
|
store.Events = append(store.Events, event)
|
|
return nil
|
|
}
|
|
|
|
type PostgresStore struct{ Pool *pgxpool.Pool }
|
|
|
|
func (store PostgresStore) Append(ctx context.Context, event Event) error {
|
|
if store.Pool == nil {
|
|
return errors.New("audit database pool is nil")
|
|
}
|
|
if event.ID == "" {
|
|
event.ID = newID()
|
|
}
|
|
if event.OccurredAt.IsZero() {
|
|
event.OccurredAt = time.Now().UTC()
|
|
}
|
|
before, err := json.Marshal(event.Before)
|
|
if err != nil {
|
|
return errors.New("marshal audit before diff")
|
|
}
|
|
after, err := json.Marshal(event.After)
|
|
if err != nil {
|
|
return errors.New("marshal audit after diff")
|
|
}
|
|
_, err = store.Pool.Exec(ctx, `INSERT INTO audit_events (id, actor, action, resource_type, resource_id, result, occurred_at, correlation_id, before_diff, after_diff) VALUES ($1::uuid, $2, $3, $4, NULLIF($5, '')::uuid, $6, $7, $8, $9::jsonb, $10::jsonb)`, event.ID, event.Actor, event.Action, event.ResourceType, event.ResourceID, event.Result, event.OccurredAt, event.CorrelationID, before, after)
|
|
if err != nil {
|
|
return errors.New("write audit event")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newID() string {
|
|
bytes := make([]byte, 16)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "00000000-0000-4000-8000-000000000000"
|
|
}
|
|
bytes[6] = (bytes[6] & 0x0f) | 0x40
|
|
bytes[8] = (bytes[8] & 0x3f) | 0x80
|
|
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", bytes[0:4], bytes[4:6], bytes[6:8], bytes[8:10], bytes[10:16])
|
|
}
|
|
|
|
func RecordSecurityAction(ctx context.Context, store Store, actor, action, result, correlationID string) error {
|
|
return store.Append(ctx, Event{Actor: actor, Action: action, ResourceType: "security", Result: result, CorrelationID: correlationID})
|
|
}
|