Ctrl K
ComunidadGuías…

pkg/audit/ignore.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package audit

import (
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	"github.com/skyhook-io/radar/pkg/resourceid"
)

// IgnoreChecksAnnotation opts a single resource out of named checks:
//
//	radarhq.io/ignore-checks: singleReplica
//	radarhq.io/ignore-checks: hostNetwork,livenessProbeMissing
//
// The value is a comma-separated list of check IDs (see CheckRegistry).
// Names that match no check are inert. The annotation must sit on the
// resource a finding is ATTRIBUTED to — for the pod-spec family that is the
// workload (Deployment/StatefulSet/...), not the Pod it generates.
//
// Annotations, not Radar-side settings: the opt-out then travels with the
// manifest, so every operator's Radar reports the same posture.
const IgnoreChecksAnnotation = "radarhq.io/ignore-checks"

// ignoreIndex maps ResourceKey → set of checkIDs that resource opts out of.
type ignoreIndex map[string]map[string]bool

func parseIgnoredChecks(value string) map[string]bool {
	if value == "" {
		return nil
	}
	ids := make(map[string]bool)
	for _, part := range strings.Split(value, ",") {
		if id := strings.TrimSpace(part); id != "" {
			ids[id] = true
		}
	}
	if len(ids) == 0 {
		return nil
	}
	return ids
}

func (idx ignoreIndex) add(kind string, obj metav1.Object) {
	idx.addKeyed(resourceid.GroupForBuiltinKind(kind), kind, obj)
}

func (idx ignoreIndex) addKeyed(group, kind string, obj metav1.Object) {
	ids := parseIgnoredChecks(obj.GetAnnotations()[IgnoreChecksAnnotation])
	if ids == nil {
		return
	}
	key := resourceid.ResourceKey(group, kind, obj.GetNamespace(), obj.GetName())
	existing := idx[key]
	if existing == nil {
		idx[key] = ids
		return
	}
	for id := range ids {
		existing[id] = true
	}
}

func (idx ignoreIndex) addUnstructured(objs []*unstructured.Unstructured) {
	for _, o := range objs {
		if o == nil {
			continue
		}
		idx.addKeyed(resourceid.GroupFromAPIVersion(o.GetAPIVersion()), o.GetKind(), o)
	}
}

// buildIgnoreIndex collects the per-resource check opt-outs from every subject
// inventory in the input. Subjects with no annotation cost one map lookup.
func buildIgnoreIndex(input *CheckInput) ignoreIndex {
	idx := ignoreIndex{}
	if input == nil {
		return idx
	}
	for _, o := range input.Pods {
		idx.add("Pod", o)
	}
	for _, o := range input.Deployments {
		idx.add("Deployment", o)
	}
	for _, o := range input.StatefulSets {
		idx.add("StatefulSet", o)
	}
	for _, o := range input.DaemonSets {
		idx.add("DaemonSet", o)
	}
	for _, o := range input.Jobs {
		idx.add("Job", o)
	}
	for _, o := range input.CronJobs {
		idx.add("CronJob", o)
	}
	for _, o := range input.Services {
		idx.add("Service", o)
	}
	for _, o := range input.Ingresses {
		idx.add("Ingress", o)
	}
	for _, o := range input.HorizontalPodAutoscalers {
		idx.add("HorizontalPodAutoscaler", o)
	}
	for _, o := range input.PodDisruptionBudgets {
		idx.add("PodDisruptionBudget", o)
	}
	for _, o := range input.ConfigMaps {
		idx.add("ConfigMap", o)
	}
	for _, o := range input.Secrets {
		idx.add("Secret", o)
	}
	for _, o := range input.ServiceAccounts {
		idx.add("ServiceAccount", o)
	}
	for _, o := range input.PersistentVolumeClaims {
		idx.add("PersistentVolumeClaim", o)
	}
	for _, o := range input.PersistentVolumes {
		idx.add("PersistentVolume", o)
	}
	idx.addUnstructured(input.ManagedResources)
	idx.addUnstructured(input.CompositeResources)
	idx.addUnstructured(input.IngressRoutes)
	idx.addUnstructured(input.MiddlewareSubjects)
	idx.addUnstructured(input.CNPGClusters)
	return idx
}

// applyIgnoreAnnotations drops findings the subject opted out of and takes the
// same subject out of that check's evaluated denominator, so a suppressed
// failure doesn't silently inflate the pass rate.
//
// Only a subject that actually FAILED leaves the denominator — the opt-out is
// applied to findings, and a passing subject produces none. An annotated
// resource that passes therefore still counts as evaluated+passed, and fixing
// an annotated resource moves it back into the denominator.
//
// Findings arrive pre-merge, so one (resource, checkID) pair can appear several
// times (one per container). The denominator is decremented once per distinct
// pair — the same grain the tracker counts in. The same distinct pairs are
// returned as checkID → namespace → count.
func applyIgnoreAnnotations(findings []Finding, tr *evalTracker, idx ignoreIndex) ([]Finding, map[string]map[string]int) {
	if len(idx) == 0 {
		return findings, nil
	}
	type checkKey struct{ resource, checkID string }
	dropped := make(map[checkKey]bool)
	var ignored map[string]map[string]int

	kept := findings[:0]
	for _, f := range findings {
		group := f.Group
		if group == "" {
			group = resourceid.GroupForBuiltinKind(f.Kind)
		}
		resource := resourceid.ResourceKey(group, f.Kind, f.Namespace, f.Name)
		if !idx[resource][f.CheckID] {
			kept = append(kept, f)
			continue
		}
		key := checkKey{resource, f.CheckID}
		if !dropped[key] {
			dropped[key] = true
			tr.unrecord(f.CheckID, f.Namespace)
			if ignored == nil {
				ignored = make(map[string]map[string]int)
			}
			if ignored[f.CheckID] == nil {
				ignored[f.CheckID] = make(map[string]int)
			}
			ignored[f.CheckID][f.Namespace]++
		}
	}
	return kept, ignored
}