Ctrl K
ComunidadGuías…

pkg/audit/types.go

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

Archivo original ↗Preguntar a KiBot
package audit

import (
	"github.com/skyhook-io/radar/pkg/checks"
	appsv1 "k8s.io/api/apps/v1"
	autoscalingv2 "k8s.io/api/autoscaling/v2"
	batchv1 "k8s.io/api/batch/v1"
	corev1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	policyv1 "k8s.io/api/policy/v1"
	storagev1 "k8s.io/api/storage/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// CheckInput contains the typed K8s resources to check.
// Each field is optional — checks are skipped for nil/empty slices.
// Callers populate this from their own cache or API client.
type CheckInput struct {
	ConfigReferenceEvidence  *ConfigReferenceEvidence
	Pods                     []*corev1.Pod
	Deployments              []*appsv1.Deployment
	ReplicaSets              []*appsv1.ReplicaSet
	StatefulSets             []*appsv1.StatefulSet
	DaemonSets               []*appsv1.DaemonSet
	Jobs                     []*batchv1.Job
	CronJobs                 []*batchv1.CronJob
	Services                 []*corev1.Service
	Ingresses                []*networkingv1.Ingress
	HorizontalPodAutoscalers []*autoscalingv2.HorizontalPodAutoscaler
	PodDisruptionBudgets     []*policyv1.PodDisruptionBudget
	ConfigMaps               []*corev1.ConfigMap
	Secrets                  []*corev1.Secret
	ServiceAccounts          []*corev1.ServiceAccount
	// ServiceAccountsNamespace is non-empty when the SA inventory covers only
	// that single namespace (namespace-scoped cache fallback). Workloads in
	// other namespaces must not have SA-dependent checks evaluated against
	// this partial inventory. Empty = cluster-wide authority (when
	// ServiceAccounts is non-nil).
	ServiceAccountsNamespace string
	LimitRanges              []*corev1.LimitRange
	PersistentVolumeClaims   []*corev1.PersistentVolumeClaim
	// PersistentVolumes are releasedPV subjects and claims' reclaim-policy
	// evidence. Nil means the inventory is unavailable; a caller not
	// authorized for cluster-scoped PVs passes an empty list, since none are
	// subjects of its scan.
	PersistentVolumes []*corev1.PersistentVolume
	StorageClasses    []*storagev1.StorageClass
	Events            []*corev1.Event
	// PVCConsumerNamespaces names namespaces with initially synced Pod and
	// built-in workload inventories. Omitted namespaces cannot prove absence;
	// CRD consumers are outside this check's coverage.
	PVCConsumerNamespaces []string
	// PVDeletionEventsComplete requires cluster-wide, initially synced Event
	// coverage. Visible warnings establish failure even with partial coverage;
	// a Failed volume's phase establishes it without any.
	PVDeletionEventsComplete bool
	// ClusterVersion is the K8s server version (e.g. "1.30"). Used for resource defaulting and deprecated API checks.
	ClusterVersion string
	// ServedAPIs lists API group/versions the cluster still serves (e.g. ["apps/v1", "batch/v1beta1"]).
	// Used to detect deprecated APIs. Callers populate from discovery client.
	ServedAPIs []string
	// ConfigObjectRefs lists ConfigMaps and Secrets referenced by non-core
	// resources that the typed Kubernetes structs above do not cover.
	ConfigObjectRefs []ConfigObjectRef

	// Crossplane resources arrive unstructured because every provider ships
	// its own CRDs — there's no typed Go schema to share across them. The
	// audit layer doesn't enumerate kinds; it inspects spec/status shape.
	// Populated by callers from a dynamic resource cache; nil when Crossplane
	// isn't installed or RBAC denies discovery.
	ManagedResources   []*unstructured.Unstructured // detected by spec.providerConfigRef (v1) or spec.crossplane.providerConfigRef (v2)
	CompositeResources []*unstructured.Unstructured // detected by spec.resourceRefs / spec.crossplane.resourceRefs; includes v1 Claims

	// Traefik CRDs arrive unstructured (no shared typed schema). Used by the
	// reference-integrity checks (route → Service/Middleware). Populated from the
	// dynamic cache; nil when Traefik isn't installed or RBAC denies it. Both the
	// traefik.io and legacy traefik.containo.us groups land here.
	IngressRoutes   []*unstructured.Unstructured // IngressRoute / IngressRouteTCP / IngressRouteUDP (scoped to audited namespaces)
	Middlewares     []*unstructured.Unstructured // Middleware / MiddlewareTCP (cluster-wide, for cross-ns ref resolution)
	TraefikServices []*unstructured.Unstructured // TraefikService (cluster-wide)
	// MiddlewareSubjects are Middleware/MiddlewareTCP in the AUDITED namespaces —
	// the subjects of the chain/errors ref-integrity checks. Distinct from
	// Middlewares (cluster-wide targets): refs resolve against the whole cluster,
	// but findings are only reported for middlewares we're auditing, so a
	// middleware in a non-audited namespace never leaks into a scoped scan.
	MiddlewareSubjects []*unstructured.Unstructured
	// TraefikAuthoritativeKinds keys (group\x00Kind) the target kinds served by a
	// synced cluster-wide informer. The dangling-ref check asserts "missing" only
	// for kinds present here — otherwise the cache may know only a subset of
	// namespaces and absence isn't conclusive. nil → assert nothing (safe default).
	TraefikAuthoritativeKinds map[string]bool
	// AllServices is the cluster-wide Service list (all namespaces) for resolving
	// Traefik route → Service references, including cross-namespace ones.
	AllServices []*corev1.Service

	// CloudNativePG CRDs arrive unstructured (no shared typed schema). Clusters
	// are the subjects of the declarative-backup check; ScheduledBackups are the
	// targets it looks for. Both nil when CNPG isn't installed or RBAC denies it.
	CNPGClusters         []*unstructured.Unstructured
	CNPGScheduledBackups []*unstructured.Unstructured
	// CNPGScheduledBackupsAuthoritative is true only when a synced cluster-wide
	// ScheduledBackup informer backs the inventory. False → the cache may know a
	// subset of namespaces, so "no schedule found" doesn't prove "none exists"
	// and the check must not run at all.
	CNPGScheduledBackupsAuthoritative bool

	// GitOpsToolsPresent gates the GitOps coverage check (checkGitOpsCoverage).
	// True when at least one GitOps root OBJECT exists (Argo CD Application,
	// Flux Kustomization, or Flux HelmRelease) — not merely the CRDs, which
	// linger after an uninstall and would gate the check open on a cluster that
	// doesn't do GitOps at all. The coverage check flags workloads NOT under
	// GitOps, so it must stay silent there, otherwise it flags everything.
	GitOpsToolsPresent bool

	// ArgoAppNames is the set of Argo CD Application names in the cluster.
	// Argo's label tracking mode (application.resourceTrackingMethod: label)
	// stamps managed resources with only app.kubernetes.io/instance — by shape
	// indistinguishable from a generic Helm/kustomize label — so the coverage
	// check treats that label as GitOps-managed only when its value names a
	// real Application.
	ArgoAppNames map[string]struct{}

	// PDPlacement is the node and volume evidence for the Persistent Disk
	// placement check. Nil when the reader can't list nodes, persistent
	// volumes and storage classes, or the cluster isn't on GKE.
	PDPlacement *PDPlacementEvidence
}

// ConfigObjectRef identifies a ConfigMap or Secret dependency.
type ConfigObjectRef struct {
	Kind      string
	Namespace string
	Name      string
}

// ScanResults is the output of RunChecks.
//
// Checks is the catalog (checkID -> definition) — kept under the "checks" JSON
// tag for back-compat with already-deployed agents/connectors. GroupedChecks is
// the remediation-queue rollup; it rides under a separate tag rather than
// renaming the catalog so older consumers don't break.
type ScanResults struct {
	Summary  ScanSummary          `json:"summary"`
	Findings []Finding            `json:"findings"`
	Groups   []ResourceGroup      `json:"groups"`
	Checks   map[string]CheckMeta `json:"checks"`
	// CheckCounts maps checkID → evaluated/passed subject counts. A check
	// appears only when it evaluated at least one subject; checks whose
	// prerequisite inputs were unavailable are absent (see MissingInputs) so
	// consumers never mistake "couldn't check" for "all passing".
	CheckCounts map[string]CheckCount `json:"checkCounts,omitempty"`
	// EvaluatedByNamespace maps checkID → namespace → distinct subjects
	// evaluated ("" namespace for cluster-scoped subjects). It exists so
	// ApplySettings can subtract ignored namespaces from the denominators
	// without re-running the scan.
	EvaluatedByNamespace map[string]map[string]int `json:"evaluatedByNamespace,omitempty"`
	// MissingInputs lists inputs unavailable to all or part of the scan
	// (RBAC denied or not loaded), e.g. "poddisruptionbudgets", "configmaps".
	// Counts describe only the subjects that could be evaluated. An entry
	// means some subject was left unevaluated; a gap in supplemental evidence
	// that still lets a finding fire is stated in that finding's message.
	MissingInputs []string `json:"missingInputs,omitempty"`
	// IgnoreChecksAnnotation names the per-resource opt-out annotation this
	// Radar honors. Its presence is what tells a newer UI the opt-out exists.
	IgnoreChecksAnnotation string `json:"ignoreChecksAnnotation,omitempty"`
	// AnnotationIgnoredByNamespace maps checkID → namespace → distinct subjects
	// whose findings were dropped by IgnoreChecksAnnotation, shaped like
	// EvaluatedByNamespace so ApplySettings filters both the same way.
	AnnotationIgnoredByNamespace map[string]map[string]int `json:"annotationIgnoredByNamespace,omitempty"`
	// GroupedChecks is the per-check remediation-queue rollup (one Check per
	// failing check). Populated by the HTTP audit handler post local-settings —
	// not by RunChecks, which doesn't carry the request context BuildChecks
	// needs. Omitted from the raw scan the Hub fan-out consumes: the Hub
	// recomputes the rollup itself after applying org Checks policy.
	GroupedChecks []checks.Check `json:"groupedChecks,omitempty"`
}

// ResourceGroup aggregates findings for a single resource.
// Groups are sorted by severity (danger first), then by name.
// Group disambiguates kinds that collide across API groups
// (e.g. core/Service vs serving.knative.dev/Service); empty for the
// core API group.
type ResourceGroup struct {
	Kind      string    `json:"kind"`
	Group     string    `json:"group,omitempty"`
	Namespace string    `json:"namespace"`
	Name      string    `json:"name"`
	Warning   int       `json:"warning"`
	Danger    int       `json:"danger"`
	Findings  []Finding `json:"findings"`
}

// CheckCount is the evaluated/passed tally for a single check. Evaluated
// counts distinct subjects at the same (resource, checkID) grain the finding
// merge uses — per-container findings collapse to their workload here too, so
// evaluated, passed, and the failing remainder all speak the same unit.
type CheckCount struct {
	Evaluated int `json:"evaluated"`
	Passed    int `json:"passed"`
}

// ScanSummary provides aggregate counts.
type ScanSummary struct {
	Passing    int                        `json:"passing"`
	Warning    int                        `json:"warning"`
	Danger     int                        `json:"danger"`
	Categories map[string]CategorySummary `json:"categories"`
}

// CategorySummary provides per-category counts.
type CategorySummary struct {
	Passing int `json:"passing"`
	Warning int `json:"warning"`
	Danger  int `json:"danger"`
}

// Finding represents a single best-practice violation.
// Group disambiguates kinds that collide across API groups
// (e.g. core/Service vs serving.knative.dev/Service); empty for the
// core API group. Check emission sites leave Group="" — buildResults
// populates it via groupForBuiltinKind so the (Kind→Group) map lives
// in one place rather than every check function.
type Finding struct {
	Kind      string `json:"kind"`
	Group     string `json:"group,omitempty"`
	Namespace string `json:"namespace"`
	Name      string `json:"name"`
	CheckID   string `json:"checkID"`
	Category  string `json:"category"` // "Security", "Reliability", "Efficiency"
	Severity  string `json:"severity"` // "warning" or "danger"
	Message   string `json:"message"`
}

// Categories
const (
	CategorySecurity    = "Security"
	CategoryReliability = "Reliability"
	CategoryEfficiency  = "Efficiency"
)

// Raw detector severities are retained as scan provenance and in existing
// audit transport/public-property compatibility contracts, especially the
// mixed-version /api/audit?raw=true Radar Hub boundary. Checks and agent
// surfaces map them to pkg/checks.Severity; visible audit presentation uses
// the mapped High/Medium labels.
const (
	SeverityWarning = "warning"
	SeverityDanger  = "danger"
)