pkg/audit/cnpg.go
Rama main · réplica de lectura del repositorio original.
package audit
import (
"github.com/skyhook-io/radar/pkg/cnpg"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const checkCNPGNoDeclarativeBackup = "cnpgNoDeclarativeBackup"
// cnpgBarmanPluginName is the barman-cloud CNPG-I plugin. Clusters that have
// migrated to it keep their backup config in an ObjectStore CR in a different
// API group, and CNPG stops publishing the in-tree RPO status fields.
const cnpgBarmanPluginName = cnpg.BarmanPluginName
// checkCNPGDeclarativeBackup flags CNPG Clusters with no ScheduledBackup
// targeting them.
//
// Deliberately narrow: the absence of a ScheduledBackup does NOT prove a
// cluster is unprotected — on-demand Backups, volume snapshots and
// plugin-external schedulers all exist — so the finding asserts only what is
// observable ("no declarative backup schedule"), at posture severity.
//
// Suspended schedules count as present. Suspension is a deliberate operator
// action, and re-reporting it here would duplicate intent as a defect.
func checkCNPGDeclarativeBackup(tr *evalTracker, input *CheckInput) []Finding {
if input == nil || len(input.CNPGClusters) == 0 {
return nil
}
// Without a synced cluster-wide ScheduledBackup informer the inventory may
// cover only a subset of namespaces, so "no schedule found" could simply
// mean "not listed". Assert nothing, and record nothing — a check that
// couldn't run must not inflate its own passed count.
if !input.CNPGScheduledBackupsAuthoritative {
return nil
}
// A ScheduledBackup and the Cluster it targets are always in the same
// namespace; spec.method may be barmanObjectStore, volumeSnapshot or plugin,
// and all three are declarative schedules.
scheduled := map[string]bool{}
for _, sb := range input.CNPGScheduledBackups {
if sb == nil {
continue
}
target, _, _ := unstructured.NestedString(sb.Object, "spec", "cluster", "name")
if target == "" {
continue
}
scheduled[sb.GetNamespace()+"\x00"+target] = true
}
var findings []Finding
for _, c := range input.CNPGClusters {
if c == nil {
continue
}
ns, name := c.GetNamespace(), c.GetName()
tr.record(checkCNPGNoDeclarativeBackup, ns)
if scheduled[ns+"\x00"+name] {
continue
}
findings = append(findings, Finding{
Kind: "Cluster",
Group: c.GroupVersionKind().Group,
Namespace: ns,
Name: name,
CheckID: checkCNPGNoDeclarativeBackup,
Category: CategoryReliability,
Severity: SeverityWarning,
Message: cnpgNoScheduleMessage(c),
})
}
return findings
}
func cnpgNoScheduleMessage(c *unstructured.Unstructured) string {
const base = "No ScheduledBackup targets this PostgreSQL cluster, so no backup schedule is declared in the cluster. " +
"On-demand Backups or an external scheduler may still cover it — this reports what is declared, not whether backups exist."
switch {
case cnpgHasBarmanPlugin(c):
return base + " Backup for this cluster runs through the barman-cloud plugin; a ScheduledBackup with spec.method: plugin is what schedules it."
case cnpgHasInTreeBackup(c):
return base + " A backup destination is configured (spec.backup.barmanObjectStore), but nothing triggers it on a schedule."
default:
return base + " No backup destination is configured on the cluster either."
}
}
func cnpgHasBarmanPlugin(c *unstructured.Unstructured) bool {
_, present := cnpg.ParseBackupDeclaration(c).BarmanPlugin()
return present
}
func cnpgHasInTreeBackup(c *unstructured.Unstructured) bool {
return cnpg.ParseBackupDeclaration(c).InTreeConfigured
}