computeFirewallDiff computes the diff between two FirewallAnalysis results. run1 is the "before" (baseline) and run2 is the "after" (comparison target). Either analysis may be nil, indicating no firewall data for that run.
(run1ID, run2ID int64, run1, run2 *FirewallAnalysis)
| 60 | // run1 is the "before" (baseline) and run2 is the "after" (comparison target). |
| 61 | // Either analysis may be nil, indicating no firewall data for that run. |
| 62 | func computeFirewallDiff(run1ID, run2ID int64, run1, run2 *FirewallAnalysis) *FirewallDiff { |
| 63 | auditDiffLog.Printf("Computing firewall diff: run1=%d, run2=%d", run1ID, run2ID) |
| 64 | diff := &FirewallDiff{ |
| 65 | Run1ID: run1ID, |
| 66 | Run2ID: run2ID, |
| 67 | } |
| 68 | |
| 69 | // Handle nil cases |
| 70 | run1Stats := make(map[string]DomainRequestStats) |
| 71 | run2Stats := make(map[string]DomainRequestStats) |
| 72 | |
| 73 | if run1 != nil { |
| 74 | run1Stats = run1.RequestsByDomain |
| 75 | } |
| 76 | if run2 != nil { |
| 77 | run2Stats = run2.RequestsByDomain |
| 78 | } |
| 79 | |
| 80 | // If both are nil/empty, return empty diff |
| 81 | if len(run1Stats) == 0 && len(run2Stats) == 0 { |
| 82 | return diff |
| 83 | } |
| 84 | |
| 85 | // Collect all domains |
| 86 | allDomains := make(map[string]struct{}) |
| 87 | for domain := range run1Stats { |
| 88 | allDomains[domain] = struct{}{} |
| 89 | } |
| 90 | for domain := range run2Stats { |
| 91 | allDomains[domain] = struct{}{} |
| 92 | } |
| 93 | |
| 94 | // Sorted domain list for deterministic output |
| 95 | sortedDomains := sliceutil.SortedKeys(allDomains) |
| 96 | |
| 97 | anomalyCount := 0 |
| 98 | |
| 99 | for _, domain := range sortedDomains { |
| 100 | stats1, inRun1 := run1Stats[domain] |
| 101 | stats2, inRun2 := run2Stats[domain] |
| 102 | |
| 103 | if !inRun1 && inRun2 { |
| 104 | // New domain in run 2 |
| 105 | entry := DomainDiffEntry{ |
| 106 | Domain: domain, |
| 107 | Status: "new", |
| 108 | Run2Allowed: stats2.Allowed, |
| 109 | Run2Blocked: stats2.Blocked, |
| 110 | Run2Status: classifyFirewallDomainStatus(stats2), |
| 111 | } |
| 112 | // Anomaly: new denied domain |
| 113 | if stats2.Blocked > 0 { |
| 114 | entry.IsAnomaly = true |
| 115 | entry.AnomalyNote = "new denied domain" |
| 116 | anomalyCount++ |
| 117 | } |
| 118 | diff.NewDomains = append(diff.NewDomains, entry) |
| 119 | } else if inRun1 && !inRun2 { |