All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 28s
CI / test-go-edu-search (push) Successful in 27s
CI / test-python-klausur (push) Successful in 1m45s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 21s
- edu-search-service von breakpilot-pwa nach breakpilot-lehrer kopiert (ohne vendor) - opensearch + edu-search-service in docker-compose.yml hinzugefuegt - voice-service aus docker-compose.yml entfernt (jetzt in breakpilot-core) - geo-service aus docker-compose.yml entfernt (nicht mehr benoetigt) - CI/CD: edu-search-service zu Gitea Actions und Woodpecker hinzugefuegt (Go lint, test mit go mod download, build, SBOM) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
256 lines
8.8 KiB
Go
256 lines
8.8 KiB
Go
package policy
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Auditor provides audit logging functionality for the policy system.
|
|
type Auditor struct {
|
|
store *Store
|
|
}
|
|
|
|
// NewAuditor creates a new Auditor instance.
|
|
func NewAuditor(store *Store) *Auditor {
|
|
return &Auditor{store: store}
|
|
}
|
|
|
|
// LogChange logs a policy change to the audit trail.
|
|
func (a *Auditor) LogChange(ctx context.Context, action AuditAction, entityType AuditEntityType, entityID *uuid.UUID, oldValue, newValue interface{}, userEmail, ipAddress, userAgent *string) error {
|
|
entry := &PolicyAuditLog{
|
|
Action: action,
|
|
EntityType: entityType,
|
|
EntityID: entityID,
|
|
UserEmail: userEmail,
|
|
IPAddress: ipAddress,
|
|
UserAgent: userAgent,
|
|
}
|
|
|
|
if oldValue != nil {
|
|
entry.OldValue = toJSON(oldValue)
|
|
}
|
|
if newValue != nil {
|
|
entry.NewValue = toJSON(newValue)
|
|
}
|
|
|
|
return a.store.CreateAuditLog(ctx, entry)
|
|
}
|
|
|
|
// LogBlocked logs a blocked URL to the blocked content log.
|
|
func (a *Auditor) LogBlocked(ctx context.Context, url, domain string, reason BlockReason, ruleID *uuid.UUID, details map[string]interface{}) error {
|
|
entry := &BlockedContentLog{
|
|
URL: url,
|
|
Domain: domain,
|
|
BlockReason: reason,
|
|
MatchedRuleID: ruleID,
|
|
}
|
|
|
|
if details != nil {
|
|
entry.Details = toJSON(details)
|
|
}
|
|
|
|
return a.store.CreateBlockedContentLog(ctx, entry)
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONVENIENCE METHODS
|
|
// =============================================================================
|
|
|
|
// LogPolicyCreated logs a policy creation event.
|
|
func (a *Auditor) LogPolicyCreated(ctx context.Context, policy *SourcePolicy, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionCreate, AuditEntitySourcePolicy, &policy.ID, nil, policy, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogPolicyUpdated logs a policy update event.
|
|
func (a *Auditor) LogPolicyUpdated(ctx context.Context, oldPolicy, newPolicy *SourcePolicy, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionUpdate, AuditEntitySourcePolicy, &newPolicy.ID, oldPolicy, newPolicy, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogPolicyDeleted logs a policy deletion event.
|
|
func (a *Auditor) LogPolicyDeleted(ctx context.Context, policy *SourcePolicy, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionDelete, AuditEntitySourcePolicy, &policy.ID, policy, nil, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogPolicyActivated logs a policy activation event.
|
|
func (a *Auditor) LogPolicyActivated(ctx context.Context, policy *SourcePolicy, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionActivate, AuditEntitySourcePolicy, &policy.ID, nil, policy, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogPolicyDeactivated logs a policy deactivation event.
|
|
func (a *Auditor) LogPolicyDeactivated(ctx context.Context, policy *SourcePolicy, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionDeactivate, AuditEntitySourcePolicy, &policy.ID, policy, nil, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogSourceCreated logs a source creation event.
|
|
func (a *Auditor) LogSourceCreated(ctx context.Context, source *AllowedSource, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionCreate, AuditEntityAllowedSource, &source.ID, nil, source, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogSourceUpdated logs a source update event.
|
|
func (a *Auditor) LogSourceUpdated(ctx context.Context, oldSource, newSource *AllowedSource, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionUpdate, AuditEntityAllowedSource, &newSource.ID, oldSource, newSource, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogSourceDeleted logs a source deletion event.
|
|
func (a *Auditor) LogSourceDeleted(ctx context.Context, source *AllowedSource, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionDelete, AuditEntityAllowedSource, &source.ID, source, nil, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogOperationUpdated logs an operation permission update event.
|
|
func (a *Auditor) LogOperationUpdated(ctx context.Context, oldOp, newOp *OperationPermission, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionUpdate, AuditEntityOperationPermission, &newOp.ID, oldOp, newOp, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogPIIRuleCreated logs a PII rule creation event.
|
|
func (a *Auditor) LogPIIRuleCreated(ctx context.Context, rule *PIIRule, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionCreate, AuditEntityPIIRule, &rule.ID, nil, rule, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogPIIRuleUpdated logs a PII rule update event.
|
|
func (a *Auditor) LogPIIRuleUpdated(ctx context.Context, oldRule, newRule *PIIRule, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionUpdate, AuditEntityPIIRule, &newRule.ID, oldRule, newRule, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogPIIRuleDeleted logs a PII rule deletion event.
|
|
func (a *Auditor) LogPIIRuleDeleted(ctx context.Context, rule *PIIRule, userEmail *string) error {
|
|
return a.LogChange(ctx, AuditActionDelete, AuditEntityPIIRule, &rule.ID, rule, nil, userEmail, nil, nil)
|
|
}
|
|
|
|
// LogContentBlocked logs a blocked content event with details.
|
|
func (a *Auditor) LogContentBlocked(ctx context.Context, url, domain string, reason BlockReason, matchedPatterns []string, ruleID *uuid.UUID) error {
|
|
details := map[string]interface{}{
|
|
"matched_patterns": matchedPatterns,
|
|
}
|
|
return a.LogBlocked(ctx, url, domain, reason, ruleID, details)
|
|
}
|
|
|
|
// LogPIIBlocked logs content blocked due to PII detection.
|
|
func (a *Auditor) LogPIIBlocked(ctx context.Context, url, domain string, matches []PIIMatch) error {
|
|
matchDetails := make([]map[string]interface{}, len(matches))
|
|
var ruleID *uuid.UUID
|
|
|
|
for i, m := range matches {
|
|
matchDetails[i] = map[string]interface{}{
|
|
"rule_name": m.RuleName,
|
|
"severity": m.Severity,
|
|
"match": maskPII(m.Match), // Mask the actual PII in logs
|
|
}
|
|
if ruleID == nil {
|
|
ruleID = &m.RuleID
|
|
}
|
|
}
|
|
|
|
details := map[string]interface{}{
|
|
"pii_matches": matchDetails,
|
|
"match_count": len(matches),
|
|
}
|
|
|
|
return a.LogBlocked(ctx, url, domain, BlockReasonPIIDetected, ruleID, details)
|
|
}
|
|
|
|
// =============================================================================
|
|
// HELPERS
|
|
// =============================================================================
|
|
|
|
// toJSON converts a value to JSON.
|
|
func toJSON(v interface{}) json.RawMessage {
|
|
data, err := json.Marshal(v)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return data
|
|
}
|
|
|
|
// maskPII masks PII data for safe logging.
|
|
func maskPII(pii string) string {
|
|
if len(pii) <= 4 {
|
|
return "****"
|
|
}
|
|
// Show first 2 and last 2 characters
|
|
return pii[:2] + "****" + pii[len(pii)-2:]
|
|
}
|
|
|
|
// =============================================================================
|
|
// AUDIT REPORT GENERATION
|
|
// =============================================================================
|
|
|
|
// AuditReport represents an audit report for compliance.
|
|
type AuditReport struct {
|
|
GeneratedAt string `json:"generated_at"`
|
|
PeriodStart string `json:"period_start"`
|
|
PeriodEnd string `json:"period_end"`
|
|
Summary AuditReportSummary `json:"summary"`
|
|
PolicyChanges []PolicyAuditLog `json:"policy_changes"`
|
|
BlockedContent []BlockedContentLog `json:"blocked_content"`
|
|
Stats *PolicyStats `json:"stats"`
|
|
}
|
|
|
|
// AuditReportSummary contains summary statistics for the audit report.
|
|
type AuditReportSummary struct {
|
|
TotalPolicyChanges int `json:"total_policy_changes"`
|
|
TotalBlocked int `json:"total_blocked"`
|
|
ChangesByAction map[string]int `json:"changes_by_action"`
|
|
BlocksByReason map[string]int `json:"blocks_by_reason"`
|
|
}
|
|
|
|
// GenerateAuditReport generates a compliance audit report.
|
|
func (a *Auditor) GenerateAuditReport(ctx context.Context, filter *AuditLogFilter, blockedFilter *BlockedContentFilter) (*AuditReport, error) {
|
|
// Get audit logs
|
|
auditLogs, _, err := a.store.ListAuditLogs(ctx, filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get blocked content
|
|
blockedLogs, _, err := a.store.ListBlockedContent(ctx, blockedFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get stats
|
|
stats, err := a.store.GetStats(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build summary
|
|
summary := AuditReportSummary{
|
|
TotalPolicyChanges: len(auditLogs),
|
|
TotalBlocked: len(blockedLogs),
|
|
ChangesByAction: make(map[string]int),
|
|
BlocksByReason: make(map[string]int),
|
|
}
|
|
|
|
for _, log := range auditLogs {
|
|
summary.ChangesByAction[string(log.Action)]++
|
|
}
|
|
|
|
for _, log := range blockedLogs {
|
|
summary.BlocksByReason[string(log.BlockReason)]++
|
|
}
|
|
|
|
// Build report
|
|
periodStart := ""
|
|
periodEnd := ""
|
|
if filter.FromDate != nil {
|
|
periodStart = filter.FromDate.Format("2006-01-02")
|
|
}
|
|
if filter.ToDate != nil {
|
|
periodEnd = filter.ToDate.Format("2006-01-02")
|
|
}
|
|
|
|
report := &AuditReport{
|
|
GeneratedAt: uuid.New().String()[:19], // Timestamp placeholder
|
|
PeriodStart: periodStart,
|
|
PeriodEnd: periodEnd,
|
|
Summary: summary,
|
|
PolicyChanges: auditLogs,
|
|
BlockedContent: blockedLogs,
|
|
Stats: stats,
|
|
}
|
|
|
|
return report, nil
|
|
}
|