Ctrl K
ComunidadGuías…

pkg/audit/gitops_coverage.go

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

Archivo original ↗Preguntar a KiBot
package audit

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

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

// GitOps coverage check IDs. Two IDs (not one) so an operator can toggle the
// stricter "nothing manages this" finding independently from the softer
// "Helm but not GitOps" one.
const (
	checkGitOpsUnmanaged = "gitops-unmanaged-workload"
	checkGitOpsHelmOnly  = "gitops-helm-only-workload"
)

// Both IDs are evaluated against the same subject, so each eligible workload is
// recorded once per ID — passed = evaluated − failed then holds for each.
var gitopsCoverageCheckIDs = []string{checkGitOpsUnmanaged, checkGitOpsHelmOnly}

// checkGitOpsCoverage flags top-level workloads that are not tracked by a GitOps
// controller — the "which of my deployments live outside Git" coverage reading.
//
// It runs only when a GitOps controller is actually installed
// (input.GitOpsToolsPresent). Without that gate, a shop that deploys with plain
// kubectl would see every workload flagged, which is noise, not a finding.
//
// Management is classified with subject.ResolveOverlay (the same tier ladder the
// topology/overlay surfaces use) rather than hand-rolled label matching:
//   - tiers 1-4 (Flux / Argo) → GitOps-managed → no finding
//   - tier 5 (native Helm, meta.helm.sh/release-name) → helm-only finding
//   - tiers 6-9 (generic app labels) or no overlay → unmanaged finding
//
// Workloads with an ownerReference are skipped: they're created by a controller
// (an operator CR, a CronJob's Jobs) whose GitOps tracking labels don't
// propagate to the children, so the coverage question belongs to the owner, not
// the child — flagging the child would be a false positive.
func checkGitOpsCoverage(tr *evalTracker, input *CheckInput) []Finding {
	if input == nil || !input.GitOpsToolsPresent {
		return nil
	}

	var findings []Finding
	emit := func(kind string, obj metav1.Object) {
		if len(obj.GetOwnerReferences()) > 0 {
			return
		}
		tr.recordAll(gitopsCoverageCheckIDs, obj.GetNamespace())

		var checkID, message string
		overlay := subject.ResolveOverlay(obj, false)
		switch {
		case overlay != nil && overlay.Winner.Tier <= subject.TierArgoInstance:
			return // GitOps-managed (Flux / Argo) — the good case, no finding.
		case overlay != nil && overlay.Winner.Tier == subject.TierInstance && argoAppTracksByLabel(input, obj):
			// Argo label-tracking mode: app.kubernetes.io/instance naming a real
			// Application IS Argo management, not a generic app label.
			return
		case overlay != nil && overlay.Winner.Tier == subject.TierHelmRelease:
			// An Argo app that deploys a Helm chart carries BOTH the Helm release
			// label and app.kubernetes.io/instance; the Helm tier wins the overlay,
			// but if the instance label names a real Application it IS GitOps-managed
			// (Argo drives the Helm release from Git), not a stray helm-CLI install.
			if argoAppTracksByLabel(input, obj) {
				return
			}
			checkID = checkGitOpsHelmOnly
			message = "Managed by Helm but not GitOps — helm-CLI upgrades bypass Git review and the declarative rollback path."
		default:
			// No overlay, or only generic app labels (tiers 6-9): nothing manages
			// this workload's desired state.
			checkID = checkGitOpsUnmanaged
			message = "Not managed by GitOps or Helm — applied imperatively, so there is no Git source of truth, drift detection, or declarative rollback."
		}

		findings = append(findings, Finding{
			Kind:      kind,
			Namespace: obj.GetNamespace(),
			Name:      obj.GetName(),
			CheckID:   checkID,
			Category:  CategoryReliability,
			Severity:  SeverityWarning,
			Message:   message,
		})
	}

	for _, d := range input.Deployments {
		emit("Deployment", d)
	}
	for _, s := range input.StatefulSets {
		emit("StatefulSet", s)
	}
	for _, ds := range input.DaemonSets {
		emit("DaemonSet", ds)
	}
	for _, cj := range input.CronJobs {
		emit("CronJob", cj)
	}
	return findings
}

// argoAppTracksByLabel reports whether the workload's app.kubernetes.io/instance
// value names an existing Argo CD Application — the signature of Argo's label
// tracking mode. Without the cross-reference, every label-tracked workload
// would be flagged as unmanaged; without requiring a real Application, every
// Helm/kustomize install carrying the standard instance label would silently
// pass as GitOps-managed.
func argoAppTracksByLabel(input *CheckInput, obj metav1.Object) bool {
	if len(input.ArgoAppNames) == 0 {
		return false
	}
	instance := obj.GetLabels()["app.kubernetes.io/instance"]
	if instance == "" {
		return false
	}
	_, ok := input.ArgoAppNames[instance]
	return ok
}