pkg/audit/types.go
Rama main · réplica de lectura del repositorio original.
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"
)