Skip to content

Commit 0452d01

Browse files
committed
fix: support metrics collection with alternate report storage
Fixes #2610 When alternate report storage is enabled, reports are saved to filesystem instead of Kubernetes CRDs. The metrics collector was only reading from CRDs, causing all metrics to become unavailable. This commit adds a storage abstraction layer that allows the metrics collector to read from either CRDs (default) or filesystem (when alternate storage is enabled), maintaining full backward compatibility. Additionally, adds validation to skip malformed reports without proper metadata (name/labels), preventing duplicate metric errors from stale files in alternate storage directories. Changes: - Add StorageReader interface for storage backend abstraction - Implement CRDStorageReader for reading from Kubernetes CRDs - Implement FilesystemStorageReader for reading from alternate storage - Add validation to filter out malformed reports without metadata - Update ResourcesMetricsCollector to use StorageReader - Add comprehensive unit tests for both storage backends All report types are supported: VulnerabilityReport, ExposedSecretReport, ConfigAuditReport, RbacAssessmentReport, InfraAssessmentReport, and ClusterComplianceReport.
1 parent 1caa4d4 commit 0452d01

File tree

5 files changed

+1150
-89
lines changed

5 files changed

+1150
-89
lines changed

pkg/configauditreport/controller/resource.go

Lines changed: 105 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ func (r *ResourceController) reconcileResource(resourceKind kube.Kind) reconcile
241241
if r.Config.AltReportStorageEnabled && r.Config.AltReportDir != "" {
242242
// Write reports to alternate storage if enabled
243243
log.V(1).Info("Writing config, infra and rbac reports to alternate storage", "dir", r.Config.AltReportDir)
244-
return r.writeAlternateReports(resource, misConfigData, log)
244+
return r.writeAlternateReports(resource, misConfigData, resourceHash, policiesHash, resourceLabelsToInclude, additionalCustomLabel, log)
245245
}
246246
// create config-audit report
247247
if !kube.IsRoleTypes(kube.Kind(kind)) || r.MergeRbacFindingWithConfigAudit {
@@ -295,7 +295,7 @@ func (r *ResourceController) reconcileResource(resourceKind kube.Kind) reconcile
295295
}
296296
}
297297

298-
func (r *ResourceController) writeAlternateReports(resource client.Object, misConfigData Misconfiguration, log logr.Logger) (ctrl.Result, error) {
298+
func (r *ResourceController) writeAlternateReports(resource client.Object, misConfigData Misconfiguration, resourceHash, policiesHash string, resourceLabelsToInclude []string, additionalCustomLabel map[string]string, log logr.Logger) (ctrl.Result, error) {
299299
// Write reports to alternate storage if enabled
300300
if r.Config.AltReportStorageEnabled && r.Config.AltReportDir != "" {
301301
// Get the report directory from the environment variable
@@ -321,42 +321,115 @@ func (r *ResourceController) writeAlternateReports(resource client.Object, misCo
321321
// Extract workload kind and name from resource labels
322322
workloadKind := resource.GetObjectKind().GroupVersionKind().Kind
323323
workloadName := resource.GetName()
324+
kind := kube.Kind(workloadKind)
324325

325-
// Write config audit report to a file
326-
configReportData, err := json.Marshal(misConfigData.configAuditReportData)
327-
if err != nil {
328-
return ctrl.Result{}, err
329-
}
330-
configReportPath := filepath.Join(configAuditDir, fmt.Sprintf("%s-%s.json", workloadKind, workloadName))
331-
err = os.WriteFile(configReportPath, configReportData, 0o600)
332-
if err != nil {
333-
return ctrl.Result{}, err
334-
}
335-
log.Info("Config audit report written", "path", configReportPath)
326+
// Build and write config audit report to a file (with full metadata)
327+
if !kube.IsRoleTypes(kind) || r.MergeRbacFindingWithConfigAudit {
328+
reportBuilder := configauditreport.NewReportBuilder(r.Client.Scheme()).
329+
Controller(resource).
330+
ResourceSpecHash(resourceHash).
331+
PluginConfigHash(policiesHash).
332+
ResourceLabelsToInclude(resourceLabelsToInclude).
333+
AdditionalReportLabels(additionalCustomLabel).
334+
Data(misConfigData.configAuditReportData)
335+
if r.Config.ScannerReportTTL != nil {
336+
reportBuilder.ReportTTL(r.Config.ScannerReportTTL)
337+
}
336338

337-
// Write infra assessment report to a file
338-
infraReportData, err := json.Marshal(misConfigData.infraAssessmentReportData)
339-
if err != nil {
340-
return ctrl.Result{}, err
341-
}
342-
infraReportPath := filepath.Join(infraAssessmentDir, fmt.Sprintf("%s-%s.json", workloadKind, workloadName))
343-
err = os.WriteFile(infraReportPath, infraReportData, 0o600)
344-
if err != nil {
345-
return ctrl.Result{}, err
339+
var configReport interface{}
340+
var err error
341+
if kube.IsClusterScopedKind(workloadKind) {
342+
configReport, err = reportBuilder.GetClusterReport()
343+
} else {
344+
configReport, err = reportBuilder.GetReport()
345+
}
346+
if err != nil {
347+
return ctrl.Result{}, fmt.Errorf("building config audit report: %w", err)
348+
}
349+
350+
configReportData, err := json.Marshal(configReport)
351+
if err != nil {
352+
return ctrl.Result{}, err
353+
}
354+
configReportPath := filepath.Join(configAuditDir, fmt.Sprintf("%s-%s.json", workloadKind, workloadName))
355+
err = os.WriteFile(configReportPath, configReportData, 0o600)
356+
if err != nil {
357+
return ctrl.Result{}, err
358+
}
359+
log.Info("Config audit report written", "path", configReportPath)
346360
}
347-
log.Info("Infra assessment report written", "path", infraReportPath)
348361

349-
// Write RBAC assessment report to a file
350-
rbacReportData, err := json.Marshal(misConfigData.rbacAssessmentReportData)
351-
if err != nil {
352-
return ctrl.Result{}, err
362+
// Build and write infra assessment report to a file (with full metadata)
363+
if k8sCoreComponent(resource) && r.Config.InfraAssessmentScannerEnabled {
364+
infraReportBuilder := infraassessment.NewReportBuilder(r.Client.Scheme()).
365+
Controller(resource).
366+
ResourceSpecHash(resourceHash).
367+
PluginConfigHash(policiesHash).
368+
ResourceLabelsToInclude(resourceLabelsToInclude).
369+
AdditionalReportLabels(additionalCustomLabel).
370+
Data(misConfigData.infraAssessmentReportData)
371+
if r.Config.ScannerReportTTL != nil {
372+
infraReportBuilder.ReportTTL(r.Config.ScannerReportTTL)
373+
}
374+
375+
var infraReport interface{}
376+
var err error
377+
if kube.IsClusterScopedKind(workloadKind) {
378+
infraReport, err = infraReportBuilder.GetClusterReport()
379+
} else {
380+
infraReport, err = infraReportBuilder.GetReport()
381+
}
382+
if err != nil {
383+
return ctrl.Result{}, fmt.Errorf("building infra assessment report: %w", err)
384+
}
385+
386+
infraReportData, err := json.Marshal(infraReport)
387+
if err != nil {
388+
return ctrl.Result{}, err
389+
}
390+
infraReportPath := filepath.Join(infraAssessmentDir, fmt.Sprintf("%s-%s.json", workloadKind, workloadName))
391+
err = os.WriteFile(infraReportPath, infraReportData, 0o600)
392+
if err != nil {
393+
return ctrl.Result{}, err
394+
}
395+
log.Info("Infra assessment report written", "path", infraReportPath)
353396
}
354-
rbacReportPath := filepath.Join(rbacAssessmentDir, fmt.Sprintf("%s-%s.json", workloadKind, workloadName))
355-
err = os.WriteFile(rbacReportPath, rbacReportData, 0o600)
356-
if err != nil {
357-
return ctrl.Result{}, err
397+
398+
// Build and write RBAC assessment report to a file (with full metadata)
399+
if kube.IsRoleTypes(kind) && r.Config.RbacAssessmentScannerEnabled && !r.MergeRbacFindingWithConfigAudit {
400+
rbacReportBuilder := rbacassessment.NewReportBuilder(r.Client.Scheme()).
401+
Controller(resource).
402+
ResourceSpecHash(resourceHash).
403+
PluginConfigHash(policiesHash).
404+
ResourceLabelsToInclude(resourceLabelsToInclude).
405+
AdditionalReportLabels(additionalCustomLabel).
406+
Data(misConfigData.rbacAssessmentReportData)
407+
if r.Config.ScannerReportTTL != nil {
408+
rbacReportBuilder.ReportTTL(r.Config.ScannerReportTTL)
409+
}
410+
411+
var rbacReport interface{}
412+
var err error
413+
if kube.IsClusterScopedKind(workloadKind) {
414+
rbacReport, err = rbacReportBuilder.GetClusterReport()
415+
} else {
416+
rbacReport, err = rbacReportBuilder.GetReport()
417+
}
418+
if err != nil {
419+
return ctrl.Result{}, fmt.Errorf("building rbac assessment report: %w", err)
420+
}
421+
422+
rbacReportData, err := json.Marshal(rbacReport)
423+
if err != nil {
424+
return ctrl.Result{}, err
425+
}
426+
rbacReportPath := filepath.Join(rbacAssessmentDir, fmt.Sprintf("%s-%s.json", workloadKind, workloadName))
427+
err = os.WriteFile(rbacReportPath, rbacReportData, 0o600)
428+
if err != nil {
429+
return ctrl.Result{}, err
430+
}
431+
log.Info("RBAC assessment report written", "path", rbacReportPath)
358432
}
359-
log.Info("RBAC assessment report written", "path", rbacReportPath)
360433
}
361434
return ctrl.Result{}, nil
362435
}

0 commit comments

Comments
 (0)